diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml index 7dfb92a6..e8c9bd0a 100644 --- a/.github/workflows/api.yaml +++ b/.github/workflows/api.yaml @@ -1,7 +1,7 @@ name: Smriti API CI on: push: - branches: + branches: - master paths: - api/** @@ -23,17 +23,17 @@ jobs: working-directory: ./api steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Golang - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: '^1.24' + go-version: "^1.25" - name: Go Version run: go version - name: Run Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.64.6 + version: v2.4.0 working-directory: api - name: Run Build run: make build @@ -55,7 +55,7 @@ jobs: working-directory: ./api steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup QEMU uses: docker/setup-qemu-action@v3 - name: Setup Docker Buildx @@ -75,4 +75,4 @@ jobs: build-args: | GITSHA=${{ github.sha }} VERSION=${ github.ref_name }} - tags: smritihq/api:${{ github.ref_name }} \ No newline at end of file + tags: smritihq/api:${{ github.ref_name }} diff --git a/.github/workflows/ml.yaml b/.github/workflows/ml.yaml index c705f9ae..84990138 100644 --- a/.github/workflows/ml.yaml +++ b/.github/workflows/ml.yaml @@ -14,11 +14,11 @@ jobs: working-directory: ./ml steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.12.x' + python-version: "3.12.x" - name: Python Version run: python -c "import sys; print(sys.version)" - name: Install Dependencies @@ -32,4 +32,3 @@ jobs: with: files: | ml/models.zip - \ No newline at end of file diff --git a/.github/workflows/pyworker.yaml b/.github/workflows/pyworker.yaml new file mode 100644 index 00000000..54057a9c --- /dev/null +++ b/.github/workflows/pyworker.yaml @@ -0,0 +1,80 @@ +name: Smriti PyWorker CI +on: + push: + branches: + - master + paths: + - pyworker/** + - .github/workflows/pyworker.yaml + pull_request: + branches: + - master + paths: + - pyworker/** + - .github/workflows/pyworker.yaml + release: + types: [published] +jobs: + ci: + name: Integration Check + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./pyworker + steps: + - name: Git Checkout + uses: actions/checkout@v5 + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12.x" + - name: Python Version + run: python -c "import sys; print(sys.version)" + - name: Install Dependencies + run: make install + - name: Run Lint + run: | + pip install pylint==3.3.4 + make lint + - name: Run Test & Cover + run: | + sudo apt-get install -y libimage-exiftool-perl exiftool libraw-dev + make test-install + make cover + - name: Publish Coverage + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: worker + files: pyworker/coverage.xml + publish: + if: ${{ (github.event_name == 'release' && github.event.action == 'published') || github.ref == 'refs/heads/master' }} + needs: ci + name: Publish Docker Image + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./pyworker + steps: + - name: Git Checkout + uses: actions/checkout@v5 + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Docker Build and Push + uses: docker/build-push-action@v6 + with: + context: worker + file: ./pyworker/Dockerfile + push: true + platforms: linux/amd64,linux/arm64/v8 + build-args: | + GITSHA=${{ github.sha }} + VERSION=${ github.ref_name }} + tags: smritihq/pyworker:${{ github.ref_name }} diff --git a/.github/workflows/worker.yaml b/.github/workflows/worker.yaml index 12576cb5..18cf791f 100644 --- a/.github/workflows/worker.yaml +++ b/.github/workflows/worker.yaml @@ -1,7 +1,7 @@ name: Smriti Worker CI on: push: - branches: + branches: - master paths: - worker/** @@ -23,24 +23,22 @@ jobs: working-directory: ./worker steps: - name: Git Checkout - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/checkout@v5 + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v2 with: - python-version: '3.12.x' - - name: Python Version - run: python -c "import sys; print(sys.version)" - - name: Install Dependencies - run: make install + cmake-version: "3.30.x" + - name: CMake Version + run: cmake --version - name: Run Lint run: | - pip install pylint==3.3.4 + pip install cpplint + cpplint --version make lint + - name: Run Build + run: make build-with-tests - name: Run Test & Cover - run: | - sudo apt-get install -y libimage-exiftool-perl exiftool libraw-dev - make test-install - make cover + run: make test - name: Publish Coverage uses: codecov/codecov-action@v5 with: @@ -57,7 +55,7 @@ jobs: working-directory: ./worker steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup QEMU uses: docker/setup-qemu-action@v3 - name: Setup Docker Buildx @@ -77,4 +75,4 @@ jobs: build-args: | GITSHA=${{ github.sha }} VERSION=${ github.ref_name }} - tags: smritihq/worker:${{ github.ref_name }} \ No newline at end of file + tags: smritihq/worker:${{ github.ref_name }} diff --git a/.gitignore b/.gitignore index d4864e0f..9854d4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ storage/* samples* models.zip -models/* \ No newline at end of file +models/* +.DS_Store +Thumbs.db +.vscode/ +.idea/ \ No newline at end of file diff --git a/Makefile b/Makefile index 275400d2..ecc2f948 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -setup: setup-api setup-worker setup-docs setup-tests +setup: setup-api setup-pyworker setup-docs setup-tests + setup-api: @cd api; \ echo "[setup-api]: Verifying go modules..."; \ @@ -7,32 +8,49 @@ setup-api: make lint; \ echo "[setup-api]: Running unit tests..."; \ make test + setup-worker: @cd worker; \ - echo "[setup-worker]: Installing requirements"; \ + echo "[setup-worker]: Installing dependencies"; \ make install; \ make test-install; \ echo "[setup-worker]: Running linter..."; \ make lint; \ echo "[setup-worker]: Running unit tests..."; \ make test + +setup-pyworker: + @cd pyworker; \ + echo "[setup-pyworker]: Installing requirements"; \ + make install; \ + make test-install; \ + echo "[setup-pyworker]: Running linter..."; \ + make lint; \ + echo "[setup-pyworker]: Running unit tests..."; \ + make test + setup-docs: @cd docs; \ echo "[setup-docs]: Installing dependencies"; \ npm install + setup-tests: @cd tests; \ echo "[setup-tests]: Installing dependencies"; \ make setup + setup-models: @echo "Setting up models..."; \ python3 scripts/setup_models.py + start: setup-models @echo "Starting smriti services..."; \ docker compose up -d + stop: @echo "Stopping smriti services..."; \ docker compose down + gen-test-data: start @echo "Generating test data"; \ python3 scripts/generate_test_data.py \ No newline at end of file diff --git a/README.md b/README.md index f121bff0..740e3843 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Smarter Home for all your Photos and Videos [![API](https://github.com/prabhuomkar/smriti/actions/workflows/api.yaml/badge.svg)](https://github.com/prabhuomkar/smriti/actions/workflows/api.yaml) -[![Worker](https://github.com/prabhuomkar/smriti/actions/workflows/worker.yaml/badge.svg)](https://github.com/prabhuomkar/smriti/actions/workflows/worker.yaml) +[![Worker](https://github.com/prabhuomkar/smriti/actions/workflows/pyworker.yaml/badge.svg)](https://github.com/prabhuomkar/smriti/actions/workflows/worker.yaml) [![Coverage](https://codecov.io/gh/prabhuomkar/smriti/branch/master/graph/badge.svg?token=D32LxO5fIj)](https://codecov.io/gh/prabhuomkar/smriti) [![License](https://img.shields.io/github/license/prabhuomkar/smriti)](LICENSE) [![Twitter](https://img.shields.io/twitter/follow/smritihq?style=social)](https://twitter.com/smritihq) diff --git a/api/.golangci.yml b/api/.golangci.yml index b94e2aaf..6062bcec 100644 --- a/api/.golangci.yml +++ b/api/.golangci.yml @@ -1,22 +1,41 @@ +version: "2" run: - timeout: 2m tests: false linters: - enable-all: true + default: all disable: - - nlreturn - - nosprintfhostport - - wsl - - wrapcheck - - exhaustivestruct + - depguard - exhaustruct - - gci - dupl - - goerr113 - - tagalign - - depguard - - interfacer - protogetter -linters-settings: - lll: - line-length: 180 + - tagalign + - wrapcheck + - wsl_v5 + - noinlineerr + - wsl + - funlen + settings: + lll: + line-length: 180 + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/api/Dockerfile b/api/Dockerfile index fb920f7b..bb9f06b9 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-alpine AS builder +FROM golang:1.25-alpine AS builder RUN apk update && apk add --no-cache git WORKDIR /app COPY go.mod go.sum ./ diff --git a/api/Makefile b/api/Makefile index fd5c1af4..28adc080 100644 --- a/api/Makefile +++ b/api/Makefile @@ -1,21 +1,36 @@ +.PHONY: build build: @go build -o api --ldflags="-X 'api/internal/models.DefaultVersion=$(VERSION)' -X 'api/internal/models.DefaultGitSHA=$(shell git rev-parse HEAD)'" +.PHONY: migrate-up +migrate-up: + @migrate -path=pkg/database/migrations -database "${DATABASE_URL}" -verbose up + +.PHONY: migrate-down +migrate-down: + @migrate -path=pkg/database/migrations -database "${DATABASE_URL}" -verbose down + +.PHONY: run run: build @./api +.PHONY: test test: @go test ./... +.PHONY: cover cover: - @go test $$(go list ./... | grep -v api/pkg/services) -race -coverprofile=coverage.txt -covermode=atomic ./... + @go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=$($$(go list ./... | grep -v api/pkg/services) | tr ' ' ',') $$(go list ./... | grep -v api/pkg/services) @go tool cover -func coverage.txt | grep total +.PHONY: cover-html cover-html: cover @go tool cover -html coverage.txt -o coverage.html +.PHONY: lint lint: @LOG_LEVEL=error golangci-lint run ./... +.PHONY: proto proto: - @protoc --proto_path=../protos ../protos/*.proto --go_out=pkg/services --go-grpc_out=pkg/services --experimental_allow_proto3_optional \ No newline at end of file + @protoc --proto_path=../protos ../protos/*.proto --go_out=pkg/services --go-grpc_out=pkg/services \ No newline at end of file diff --git a/api/config/config.go b/api/config/config.go index 06b3c63c..cde45ca9 100644 --- a/api/config/config.go +++ b/api/config/config.go @@ -6,8 +6,7 @@ import ( "github.com/kelseyhightower/envconfig" ) -type ( - // Log ... +type ( // Log ... Log struct { Level string `envconfig:"SMRITI_LOG_LEVEL" default:"INFO"` } @@ -26,19 +25,20 @@ type ( // Database ... Database struct { - LogLevel string `envconfig:"SMRITI_DATABASE_LOG_LEVEL" default:"ERROR"` - Host string `envconfig:"SMRITI_DATABASE_HOST" default:"database"` - Port int `envconfig:"SMRITI_DATABASE_PORT" default:"5432"` - Username string `envconfig:"SMRITI_DATABASE_USERNAME" default:"smritiuser"` - Password string `envconfig:"SMRITI_DATABASE_PASSWORD" default:"smritipass"` - Name string `envconfig:"SMRITI_DATABASE_NAME" default:"smriti"` + LogLevel string `envconfig:"SMRITI_DATABASE_LOG_LEVEL" default:"ERROR"` + Host string `envconfig:"SMRITI_DATABASE_HOST" default:"database"` + Port int `envconfig:"SMRITI_DATABASE_PORT" default:"5432"` + Username string `envconfig:"SMRITI_DATABASE_USERNAME" default:"smritiuser"` + Password string `envconfig:"SMRITI_DATABASE_PASSWORD" default:"smritipass"` + Name string `envconfig:"SMRITI_DATABASE_NAME" default:"smriti"` + Timeout time.Duration `envconfig:"SMRITI_DATABASE_TIMEOUT" default:"10s"` } // Cache ... Cache struct { - Type string `envconfig:"SMRITI_CACHE_TYPE" default:"inmemory"` - Host string `envconfig:"SMRITI_CACHE_HOST" default:"cache"` - Port int `envconfig:"SMRITI_CACHE_PORT" default:"6379"` + Type string `envconfig:"SMRITI_CACHE_TYPE" default:"inmemory"` + Host string `envconfig:"SMRITI_CACHE_HOST" default:"cache"` + Port int `envconfig:"SMRITI_CACHE_PORT" default:"6379"` Password string `envconfig:"SMRITI_CACHE_PASSWORD" default:"smritipass"` } @@ -50,45 +50,42 @@ type ( // Auth ... Auth struct { - Enabled bool `envconfig:"SMRITI_AUTH_ENABLED" default:"false"` - Issuer string `envconfig:"SMRITI_AUTH_ISSUER" default:"smriti"` - Audience string `envconfig:"SMRITI_AUTH_AUDIENCE" default:"smriti"` - AccessTTL int `envconfig:"SMRITI_AUTH_ACCESS_TTL" default:"3600"` + Enabled bool `envconfig:"SMRITI_AUTH_ENABLED" default:"false"` + Issuer string `envconfig:"SMRITI_AUTH_ISSUER" default:"smriti"` + Audience string `envconfig:"SMRITI_AUTH_AUDIENCE" default:"smriti"` + AccessTTL int `envconfig:"SMRITI_AUTH_ACCESS_TTL" default:"3600"` RefreshTTL int `envconfig:"SMRITI_AUTH_REFRESH_TTL" default:"86400"` - Secret string `envconfig:"SMRITI_AUTH_SECRET" default:"smriti"` + Secret string `envconfig:"SMRITI_AUTH_SECRET" default:"smriti"` } // ML ... ML struct { - Places bool `envconfig:"SMRITI_ML_PLACES" default:"true"` - Classification bool `envconfig:"SMRITI_ML_CLASSIFICATION" default:"true"` - OCR bool `envconfig:"SMRITI_ML_OCR" default:"true"` - Search bool `envconfig:"SMRITI_ML_SEARCH" default:"true"` - Faces bool `envconfig:"SMRITI_ML_FACES" default:"true"` - PlacesProvider string `envconfig:"SMRITI_ML_PLACES_PROVIDER" default:"openstreetmap"` - ClassificationProvider string `envconfig:"SMRITI_ML_CLASSIFICATION_PROVIDER" default:"pytorch"` - ClassificationParams string `envconfig:"SMRITI_ML_CLASSIFICATION_PARAMS" default:"{\"file\":\"classification_v240624.pt\"}"` - OCRProvider string `envconfig:"SMRITI_ML_OCR_PROVIDER" default:"paddlepaddle"` - OCRParams string `envconfig:"SMRITI_ML_OCR_PARAMS" default:"{\"det_model_dir\":\"det_onnx\",\"rec_model_dir\":\"rec_onnx\",\"cls_model_dir\":\"cls_onnx\"}"` - SearchProvider string `envconfig:"SMRITI_ML_SEARCH_PROVIDER" default:"pytorch"` - SearchParams string `envconfig:"SMRITI_ML_SEARCH_PARAMS" default:"{\"tokenizer_dir\":\"search_tokenizer\",\"processor_dir\":\"search_processor\",\"text_file\":\"search_text_v240624.pt\",\"vision_file\":\"search_vision_v240624.pt\"}"` //nolint:lll - FacesProvider string `envconfig:"SMRITI_ML_FACES_PROVIDER" default:"pytorch"` - FacesParams string `envconfig:"SMRITI_ML_FACES_PARAMS" default:"{\"minutes\":\"1\",\"face_threshold\":\"0.9\",\"model\":\"vggface2\",\"clustering\":\"annoy\"}"` - PreviewThumbnailParams string `envconfig:"SMRITI_ML_PREVIEW_THUMBNAIL_PARAMS" default:"{\"thumbnail_size\":\"512\"}"` + Places bool `envconfig:"SMRITI_ML_PLACES" default:"true"` + Classification bool `envconfig:"SMRITI_ML_CLASSIFICATION" default:"true"` + OCR bool `envconfig:"SMRITI_ML_OCR" default:"true"` + Search bool `envconfig:"SMRITI_ML_SEARCH" default:"true"` + Faces bool `envconfig:"SMRITI_ML_FACES" default:"true"` + PlacesProvider string `envconfig:"SMRITI_ML_PLACES_PROVIDER" default:"openstreetmap"` + OCRProvider string `envconfig:"SMRITI_ML_OCR_PROVIDER" default:"paddlepaddle"` + OCRParams string `envconfig:"SMRITI_ML_OCR_PARAMS" default:"{\"det_model_dir\":\"det_onnx\",\"rec_model_dir\":\"rec_onnx\",\"cls_model_dir\":\"cls_onnx\"}"` //nolint:lll + SearchProvider string `envconfig:"SMRITI_ML_SEARCH_PROVIDER" default:"pytorch"` + SearchParams string `envconfig:"SMRITI_ML_SEARCH_PARAMS" default:"{\"tokenizer_dir\":\"search_tokenizer\",\"processor_dir\":\"search_processor\",\"text_file\":\"search_text_v240624.pt\",\"vision_file\":\"search_vision_v240624.pt\"}"` //nolint:lll + FacesProvider string `envconfig:"SMRITI_ML_FACES_PROVIDER" default:"pytorch"` + FacesParams string `envconfig:"SMRITI_ML_FACES_PARAMS" default:"{\"minutes\":\"1\",\"face_threshold\":\"0.9\",\"model\":\"vggface2\",\"clustering\":\"annoy\"}"` //nolint:lll + PreviewThumbnailParams string `envconfig:"SMRITI_ML_PREVIEW_THUMBNAIL_PARAMS" default:"{\"image_quality\":50,\"thumbnail_size\":256,\"placeholder_size\":2}"` //nolint:lll } // Feature ... Feature struct { Favourites bool `envconfig:"SMRITI_FEATURE_FAVOURITES" default:"true"` - Hidden bool `envconfig:"SMRITI_FEATURE_HIDDEN" default:"true"` - Trash bool `envconfig:"SMRITI_FEATURE_TRASH" default:"true"` - Albums bool `envconfig:"SMRITI_FEATURE_ALBUMS" default:"true"` - Explore bool `envconfig:"SMRITI_FEATURE_EXPLORE" default:"true"` - Places bool `envconfig:"SMRITI_FEATURE_PLACES" default:"true"` - Things bool `envconfig:"SMRITI_FEATURE_THINGS" default:"true"` - People bool `envconfig:"SMRITI_FEATURE_PEOPLE" default:"true"` - Sharing bool `envconfig:"SMRITI_FEATURE_SHARING" default:"true"` - Jobs bool `envconfig:"SMRITI_FEATURE_JOBS" default:"true"` + Hidden bool `envconfig:"SMRITI_FEATURE_HIDDEN" default:"true"` + Trash bool `envconfig:"SMRITI_FEATURE_TRASH" default:"true"` + Albums bool `envconfig:"SMRITI_FEATURE_ALBUMS" default:"true"` + Explore bool `envconfig:"SMRITI_FEATURE_EXPLORE" default:"true"` + Places bool `envconfig:"SMRITI_FEATURE_PLACES" default:"true"` + People bool `envconfig:"SMRITI_FEATURE_PEOPLE" default:"true"` + Sharing bool `envconfig:"SMRITI_FEATURE_SHARING" default:"true"` + Jobs bool `envconfig:"SMRITI_FEATURE_JOBS" default:"true"` } // Admin ... @@ -99,16 +96,16 @@ type ( // Storage ... Storage struct { - Provider string `envconfig:"SMRITI_STORAGE_PROVIDER" default:"disk"` - DiskRoot string `envconfig:"SMRITI_STORAGE_DISK_ROOT" default:"../storage"` - Endpoint string `envconfig:"SMRITI_STORAGE_ENDPOINT" default:"storage:9000"` + Provider string `envconfig:"SMRITI_STORAGE_PROVIDER" default:"disk"` + DiskRoot string `envconfig:"SMRITI_STORAGE_DISK_ROOT" default:"../storage"` + Endpoint string `envconfig:"SMRITI_STORAGE_ENDPOINT" default:"storage:9000"` AccessKey string `envconfig:"SMRITI_STORAGE_ACCESS_KEY" default:"smritiuser"` SecretKey string `envconfig:"SMRITI_STORAGE_SECRET_KEY" default:"smritipass"` } Job struct { QueueInterval time.Duration `envconfig:"SMRITI_JOB_QUEUE_INTERVAL" default:"5s"` - Concurrency int `envconfig:"SMRITI_JOB_CONCURRENCY" default:"10"` + Concurrency int `envconfig:"SMRITI_JOB_CONCURRENCY" default:"10"` } // Config ... @@ -135,5 +132,6 @@ func Init() (*Config, error) { if err != nil { return nil, err } + return &cfg, nil } diff --git a/api/config/config_test.go b/api/config/config_test.go new file mode 100644 index 00000000..c8715e33 --- /dev/null +++ b/api/config/config_test.go @@ -0,0 +1,49 @@ +package config + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + tests := []struct { + Name string + MockFunc func() func() + WantErr bool + }{ + { + "error due to invalid feature boolean value", func() func() { + os.Setenv("SMRITI_FEATURE_FAVOURITES", "invalid") + return func() { + os.Setenv("SMRITI_FEATURE_FAVOURITES", "") + } + }, true, + }, + { + "success", func() func() { + os.Setenv("SMRITI_FEATURE_FAVOURITES", "true") + return func() { + os.Setenv("SMRITI_FEATURE_FAVOURITES", "") + } + }, false, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + if test.MockFunc != nil { + defer test.MockFunc()() + } + cfg, err := Init() + if test.WantErr { + assert.Nil(t, cfg) + assert.Error(t, err) + } else { + assert.NotNil(t, cfg) + assert.NoError(t, err) + } + }) + } +} diff --git a/api/go.mod b/api/go.mod index 5dc62d37..464918a6 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,30 +1,26 @@ module api -go 1.23.0 - -toolchain go1.24.1 +go 1.25.0 require ( - github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/bluele/gcache v0.0.2 - github.com/fsnotify/fsnotify v1.8.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 + github.com/jackc/pgx/v5 v5.7.5 github.com/kelseyhightower/envconfig v1.4.0 - github.com/labstack/echo-contrib v0.17.2 - github.com/labstack/echo/v4 v4.13.3 - github.com/minio/minio-go/v7 v7.0.87 - github.com/pgvector/pgvector-go v0.2.3 - github.com/prometheus/client_golang v1.21.1 + github.com/labstack/echo-contrib v0.17.4 + github.com/labstack/echo/v4 v4.13.4 + github.com/minio/minio-go/v7 v7.0.92 + github.com/pashagolub/pgxmock/v4 v4.7.0 + github.com/pgvector/pgvector-go v0.3.0 + github.com/prometheus/client_golang v1.22.0 github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.10.0 go.uber.org/automaxprocs v1.6.0 - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 - google.golang.org/grpc v1.71.0 - google.golang.org/protobuf v1.36.5 - gorm.io/driver/postgres v1.5.11 - gorm.io/gorm v1.25.12 + google.golang.org/grpc v1.75.1 + google.golang.org/protobuf v1.36.9 + gorm.io/gorm v1.30.0 ) require ( @@ -33,38 +29,40 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/kr/text v0.2.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/minio/crc64nvme v1.0.1 // indirect + github.com/minio/crc64nvme v1.0.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.64.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/tinylib/msgp v1.3.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.11.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.11 // indirect ) diff --git a/api/go.sum b/api/go.sum index 7fb94d5d..fe7d129f 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,14 +1,11 @@ -entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= -entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ= +entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -20,8 +17,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -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-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0= @@ -32,24 +29,24 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 h1:KcFzXwzM/kGhIRHvc8jdixfIJjVzuUJdnv+5xsPutog= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= -github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -71,10 +68,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w= -github.com/labstack/echo-contrib v0.17.2/go.mod h1:NeDh3PX7j/u+jR4iuDt1zHmWZSCz9c/p9mxXcDpyS8E= -github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= +github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= +github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= +github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -83,12 +80,12 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= -github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg= +github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ= -github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg= +github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw= +github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -97,22 +94,26 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/pgvector/pgvector-go v0.2.3 h1:/vv4mmSAtkT/XHCwkPexNiI1SNmrwccUqxPYr9WzIek= -github.com/pgvector/pgvector-go v0.2.3/go.mod h1:u5sg3z9bnqVEdpe1pkTij8/rFhTaMCMNyQagPDLK8gQ= +github.com/pashagolub/pgxmock/v4 v4.7.0 h1:de2ORuFYyjwOQR7NBm57+321RnZxpYiuUjsmqRiqgh8= +github.com/pashagolub/pgxmock/v4 v4.7.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM= +github.com/pgvector/pgvector-go v0.3.0 h1:Ij+Yt78R//uYqs3Zk35evZFvr+G0blW0OUN+Q2D1RWc= +github.com/pgvector/pgvector-go v0.3.0/go.mod h1:duFy+PXWfW7QQd5ibqutBO4GxLsUZ9RVXhFZGIBsWSA= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= 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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -122,6 +123,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ= @@ -142,41 +145,43 @@ github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vb github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -189,7 +194,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= diff --git a/api/internal/auth/auth.go b/api/internal/auth/auth.go index 4c526b18..4f2de8b9 100644 --- a/api/internal/auth/auth.go +++ b/api/internal/auth/auth.go @@ -5,36 +5,40 @@ import ( "api/internal/models" "api/pkg/cache" "errors" + "log/slog" "time" "github.com/go-redis/redis/v8" "github.com/golang-jwt/jwt/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" ) -type ( - // TokenClaims ... +type ( // TokenClaims ... TokenClaims struct { + jwt.RegisteredClaims + ID string `json:"id"` Username string `json:"username"` Features string `json:"features"` - jwt.RegisteredClaims } ) +var errNilUserID = errors.New("got nil user id") + // GetTokens ... func GetTokens(cfg *config.Config, cache cache.Provider, user models.User) (string, string, error) { accessToken, refreshToken := GetAccessAndRefreshTokens(cfg, user) - setRefreshErr := cache.SetWithExpire(refreshToken, true, time.Duration(cfg.Auth.RefreshTTL)*time.Second) + setRefreshErr := cache.SetWithExpire(refreshToken, true, time.Duration(cfg.RefreshTTL)*time.Second) if setRefreshErr != nil { slog.Error("error caching refresh token", "error", setRefreshErr) + return "", "", setRefreshErr } - setAccessErr := cache.SetWithExpire(accessToken, refreshToken, time.Duration(cfg.Auth.AccessTTL)*time.Second) + setAccessErr := cache.SetWithExpire(accessToken, refreshToken, time.Duration(cfg.AccessTTL)*time.Second) if setAccessErr != nil { slog.Error("error caching refresh token", "error", setAccessErr) + return "", "", setAccessErr } @@ -45,21 +49,24 @@ func GetTokens(cfg *config.Config, cache cache.Provider, user models.User) (stri func RefreshTokens(cfg *config.Config, cache cache.Provider, refreshToken string) (string, string, error) { if _, err := cache.Get(refreshToken); err != nil { slog.Error("error getting refresh token from cache", "error", err) + return "", "", err } claims, err := getClaimsFromToken(cfg, refreshToken) if err != nil { slog.Error("error getting claims from token", "error", err) + return "", "", err } userID, err := uuid.FromString(claims.ID) if err != nil || userID == uuid.Nil { if err == nil { - err = errors.New("got nil user id") + err = errNilUserID } slog.Error("error getting user id from claims", "userId", userID, "error", err) + return "", "", err } @@ -73,6 +80,7 @@ func RemoveTokens(cache cache.Provider, accessToken string) error { if !errors.Is(err, redis.Nil) { slog.Error("error getting access token from cache", "error", err) } + return err } @@ -90,12 +98,14 @@ func VerifyToken(cfg *config.Config, cache cache.Provider, accessToken string) ( if !errors.Is(err, redis.Nil) { slog.Error("error getting access token from cache", "error", err) } + return nil, err } claims, err := getClaimsFromToken(cfg, accessToken) if err != nil { slog.Error("error getting claims from token", "error", err) + return nil, err } @@ -108,16 +118,18 @@ func GetAccessAndRefreshTokens(cfg *config.Config, user models.User) (string, st func getClaimsFromToken(cfg *config.Config, token string) (*TokenClaims, error) { parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, func(*jwt.Token) (interface{}, error) { - return []byte(cfg.Auth.Secret), nil + return []byte(cfg.Secret), nil }) if err != nil || !parsedToken.Valid { slog.Error("error parsing claims from token", "error", err) + return nil, err } claims, ok := parsedToken.Claims.(*TokenClaims) if !ok { slog.Error("error getting claims from token", "error", err) + return nil, err } @@ -125,25 +137,20 @@ func getClaimsFromToken(cfg *config.Config, token string) (*TokenClaims, error) } func getSignedToken(cfg *config.Config, user models.User, subject string) string { - ttl := cfg.Auth.AccessTTL + ttl := cfg.AccessTTL if subject == "refresh" { - ttl = cfg.Auth.RefreshTTL + ttl = cfg.RefreshTTL } creationTime := time.Now() token := jwt.NewWithClaims(jwt.SigningMethodHS256, TokenClaims{ - user.ID.String(), - user.Username, - user.Features, jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(creationTime.Add(time.Duration(ttl) * time.Second)), - IssuedAt: jwt.NewNumericDate(creationTime), - NotBefore: jwt.NewNumericDate(creationTime), - Issuer: cfg.Auth.Issuer, - Audience: []string{cfg.Auth.Audience}, - Subject: subject, - ID: user.ID.String(), + IssuedAt: jwt.NewNumericDate(creationTime), NotBefore: jwt.NewNumericDate(creationTime), + Issuer: cfg.Issuer, Audience: []string{cfg.Audience}, Subject: subject, ID: user.ID.String(), }, + user.ID.String(), user.Username, user.Features, }) - signedToken, _ := token.SignedString([]byte(cfg.Auth.Secret)) + signedToken, _ := token.SignedString([]byte(cfg.Secret)) + return signedToken } diff --git a/api/internal/auth/auth_test.go b/api/internal/auth/auth_test.go index ddd5d4be..8d662f98 100644 --- a/api/internal/auth/auth_test.go +++ b/api/internal/auth/auth_test.go @@ -1,13 +1,12 @@ package auth import ( - "errors" - "testing" - "time" - "api/config" "api/internal/models" "api/pkg/cache" + "errors" + "testing" + "time" "github.com/bluele/gcache" uuid "github.com/satori/go.uuid" @@ -21,38 +20,36 @@ func TestGetTokens(t *testing.T) { WantErr bool }{ { - "success", - nil, - false, + "success", nil, false, }, { - "error caching refresh token", - func(a interface{}, b interface{}) (interface{}, error) { + "error caching refresh token", func(a interface{}, b interface{}) (interface{}, error) { val, ok := b.(bool) if ok && val == true { return nil, errors.New("some cache error") } return b, nil - }, - true, + }, true, }, { - "error caching access token", - func(a interface{}, b interface{}) (interface{}, error) { + "error caching access token", func(a interface{}, b interface{}) (interface{}, error) { val, ok := b.(bool) if ok && val == true { return b, nil } return nil, errors.New("some cache error") - }, - true, + }, true, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { cfg := &config.Config{} - cache := &cache.InMemoryCache{Connection: gcache.New(1024).LRU().SerializeFunc(test.SerializeFunc).Build()} - atoken, rtoken, err := GetTokens(cfg, cache, models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179")}) + cache := &cache.InMemoryCache{ + Connection: gcache.New(1024).LRU().SerializeFunc(test.SerializeFunc).Build(), + } + atoken, rtoken, err := GetTokens(cfg, cache, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), + }) if test.WantErr { assert.Empty(t, atoken) assert.Empty(t, rtoken) @@ -75,45 +72,33 @@ func TestRefreshTokens(t *testing.T) { WantErr bool }{ { - "success", - func(cfg *config.Config, cache cache.Provider) string { - _, oldRToken := GetAccessAndRefreshTokens(cfg, models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username"}) + "success", func(cfg *config.Config, cache cache.Provider) string { + _, oldRToken := GetAccessAndRefreshTokens(cfg, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username", + }) _ = cache.SetWithExpire(oldRToken, true, 1*time.Minute) return oldRToken - }, - nil, - nil, - false, + }, nil, nil, false, }, { - "error getting refresh token", - func(cfg *config.Config, cache cache.Provider) string { + "error getting refresh token", func(cfg *config.Config, cache cache.Provider) string { return "badToken" - }, - nil, - nil, - true, + }, nil, nil, true, }, { - "error parsing claims from token", - func(cfg *config.Config, cache cache.Provider) string { + "error parsing claims from token", func(cfg *config.Config, cache cache.Provider) string { _ = cache.SetWithExpire("badToken", true, 1*time.Minute) return "badToken" - }, - nil, - nil, - true, + }, nil, nil, true, }, { - "error getting user id from claims", - func(cfg *config.Config, cache cache.Provider) string { - _, oldRToken := GetAccessAndRefreshTokens(cfg, models.User{ID: uuid.FromStringOrNil("invalid-user-id"), Username: "username"}) + "error getting user id from claims", func(cfg *config.Config, cache cache.Provider) string { + _, oldRToken := GetAccessAndRefreshTokens(cfg, models.User{ + ID: uuid.FromStringOrNil("invalid-user-id"), Username: "username", + }) _ = cache.SetWithExpire(oldRToken, true, 1*time.Minute) return oldRToken - }, - nil, - nil, - true, + }, nil, nil, true, }, } for _, test := range tests { @@ -149,24 +134,18 @@ func TestRemoveTokens(t *testing.T) { WantErr bool }{ { - "success", - func(cfg *config.Config, cache cache.Provider) string { - oldAToken, _ := GetAccessAndRefreshTokens(cfg, models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username"}) + "success", func(cfg *config.Config, cache cache.Provider) string { + oldAToken, _ := GetAccessAndRefreshTokens(cfg, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username", + }) _ = cache.SetWithExpire(oldAToken, true, 1*time.Minute) return oldAToken - }, - nil, - nil, - false, + }, nil, nil, false, }, { - "error getting refresh token", - func(cfg *config.Config, cache cache.Provider) string { + "error getting refresh token", func(cfg *config.Config, cache cache.Provider) string { return "badToken" - }, - nil, - nil, - true, + }, nil, nil, true, }, } for _, test := range tests { @@ -198,34 +177,24 @@ func TestVerifyToken(t *testing.T) { WantErr bool }{ { - "success", - func(cfg *config.Config, cache cache.Provider) string { - oldAToken, _ := GetAccessAndRefreshTokens(cfg, models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username"}) + "success", func(cfg *config.Config, cache cache.Provider) string { + oldAToken, _ := GetAccessAndRefreshTokens(cfg, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username", + }) _ = cache.SetWithExpire(oldAToken, true, 1*time.Minute) return oldAToken - }, - nil, - nil, - false, + }, nil, nil, false, }, { - "error getting access token", - func(cfg *config.Config, cache cache.Provider) string { + "error getting access token", func(cfg *config.Config, cache cache.Provider) string { return "badToken" - }, - nil, - nil, - true, + }, nil, nil, true, }, { - "error parsing claims from token", - func(cfg *config.Config, cache cache.Provider) string { + "error parsing claims from token", func(cfg *config.Config, cache cache.Provider) string { _ = cache.SetWithExpire("badToken", true, 1*time.Minute) return "badToken" - }, - nil, - nil, - true, + }, nil, nil, true, }, } for _, test := range tests { diff --git a/api/internal/handlers/albums.go b/api/internal/handlers/albums.go index 0a3c944a..f1b188e0 100644 --- a/api/internal/handlers/albums.go +++ b/api/internal/handlers/albums.go @@ -3,17 +3,18 @@ package handlers import ( "api/internal/models" "errors" + "fmt" + "log/slog" "net/http" "reflect" + "time" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) -type ( - // AlbumRequest ... +type ( // AlbumRequest ... AlbumRequest struct { Name *string `json:"name"` Description *string `json:"description"` @@ -28,6 +29,29 @@ type ( } ) +const ( + queryGetAlbumMediaItems = `SELECT * FROM mediaitems WHERE id IN (SELECT mediaitem_id FROM album_mediaitems WHERE user_id=$1` + + ` AND album_id=$2) AND is_hidden=false AND is_deleted=false ORDER BY created_at DESC OFFSET $3 LIMIT $4` + queryAddAlbumMediaItems = `INSERT INTO album_mediaitems (album_id, mediaitem_id) VALUES ($1, $2)` + queryRemoveAlbumMediaItems = `DELETE FROM album_mediaitems WHERE album_id=$1 AND mediaitem_id=$2` + queryGetAlbumMediaItemIDAndCount = `SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems WHERE` + + ` album_id=$1 LIMIT 1` + queryUpdateAlbumMediaItems = `UPDATE albums SET mediaitems_count=$1, cover_mediaitem_id=CASE WHEN cover_mediaitem_id` + + ` IS NOT NULL AND $2::uuid IS NULL THEN NULL ELSE COALESCE($2::uuid, cover_mediaitem_id) END WHERE user_id=$3 AND id=$4` + queryGetAlbum = `SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems m` + + ` ON a.cover_mediaitem_id=m.id WHERE a.user_id=$1 AND a.id=$2` + queryGetAlbums = `SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems m` + + ` ON a.cover_mediaitem_id=m.id WHERE a.user_id=$1 AND a.is_hidden=false AND a.is_shared=$2 ORDER BY a.%s` + + ` OFFSET $3 LIMIT $4` + queryUpdateAlbum = `UPDATE albums SET name=$3, description=$4, is_shared=$5, is_hidden=$6,` + + ` cover_mediaitem_id=$7, updated_at=$8 WHERE user_id=$1 AND id=$2` + queryDeleteAlbum = `DELETE FROM albums WHERE user_id=$1 AND id=$2` + queryCreateAlbum = `INSERT INTO albums (id, user_id, name, description, is_shared, is_hidden,` + + ` created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` +) + // GetAlbumMediaItems ... func (h *Handler) GetAlbumMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) @@ -36,20 +60,29 @@ func (h *Handler) GetAlbumMediaItems(ctx echo.Context) error { if err != nil { return err } - album := new(models.Album) - album.ID = uid - album.UserID = userID mediaItems := []models.MediaItem{} - err = h.DB.Model(&album).Offset(offset).Limit(limit).Association("MediaItems").Find(&mediaItems, "is_hidden=? AND is_deleted=?", false, false) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetAlbumMediaItems, userID, uid, offset, limit) if err != nil { slog.Error("error getting album mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning album mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) + } + return ctx.JSON(http.StatusOK, mediaItems) } // AddAlbumMediaItems ... -func (h *Handler) AddAlbumMediaItems(ctx echo.Context) error { +func (h *Handler) AddAlbumMediaItems(ctx echo.Context) error { //nolint:cyclop userID := getRequestingUserID(ctx) uid, err := getAlbumID(ctx) if err != nil { @@ -59,27 +92,51 @@ func (h *Handler) AddAlbumMediaItems(ctx echo.Context) error { if err != nil { return err } - album := new(models.Album) - album.ID = uid - album.UserID = userID - err = h.DB.Omit("MediaItems.*").Model(&album).Association("MediaItems").Append(mediaItems) + atx, err := h.DB.Begin(ctx.Request().Context()) if err != nil { - slog.Error("error adding album mediaitems", "error", err) + slog.Error("error starting transaction for adding album mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - mediaItemCount := int(h.DB.Model(&album).Association("MediaItems").Count()) - album.MediaItemsCount = &mediaItemCount - album.CoverMediaItemID = &mediaItems[len(mediaItems)-1].ID - result := h.DB.Model(&album).Omit("MediaItems").Updates(album) - if result.Error != nil { - slog.Error("error updating album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + defer func() { + if err != nil { + _ = atx.Rollback(ctx.Request().Context()) + } + }() + for _, mediaItem := range mediaItems { + _, err = atx.Exec(ctx.Request().Context(), queryAddAlbumMediaItems, uid, mediaItem.ID) + if err != nil { + slog.Error("error adding album mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + } + var coverMediaItemID *uuid.UUID + mediaItemsCount := 0 + err = atx.QueryRow(ctx.Request().Context(), queryGetAlbumMediaItemIDAndCount, uid). + Scan(&coverMediaItemID, &mediaItemsCount) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + slog.Error("error getting album mediaitem id and count", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + _, err = atx.Exec(ctx.Request().Context(), queryUpdateAlbumMediaItems, mediaItemsCount, coverMediaItemID, userID, uid) + if err != nil { + slog.Error("error updating album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + if err = atx.Commit(ctx.Request().Context()); err != nil { + slog.Error("error committing transaction for adding album mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return ctx.JSON(http.StatusNoContent, nil) } // RemoveAlbumMediaItems ... -func (h *Handler) RemoveAlbumMediaItems(ctx echo.Context) error { +func (h *Handler) RemoveAlbumMediaItems(ctx echo.Context) error { //nolint:cyclop userID := getRequestingUserID(ctx) uid, err := getAlbumID(ctx) if err != nil { @@ -89,34 +146,47 @@ func (h *Handler) RemoveAlbumMediaItems(ctx echo.Context) error { if err != nil { return err } - album := new(models.Album) - album.ID = uid - album.UserID = userID - err = h.DB.Omit("MediaItems.*").Model(&album).Association("MediaItems").Delete(mediaItems) + atx, err := h.DB.Begin(ctx.Request().Context()) if err != nil { - slog.Error("error removing album mediaitems", "error", err) + slog.Error("error starting transaction for removing album mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - newCoverMediaItem := models.MediaItem{} - err = h.DB.Model(&album).Association("MediaItems").Find(&newCoverMediaItem) - if err != nil { - slog.Error("error getting new album cover mediaitem", "error", err) + defer func() { + if err != nil { + _ = atx.Rollback(ctx.Request().Context()) + } + }() + for _, mediaItem := range mediaItems { + _, err = atx.Exec(ctx.Request().Context(), queryRemoveAlbumMediaItems, uid, mediaItem.ID) + if err != nil { + slog.Error("error removing album mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + } + var coverMediaItemID *uuid.UUID + mediaItemsCount := 0 + err = atx.QueryRow(ctx.Request().Context(), queryGetAlbumMediaItemIDAndCount, uid). + Scan(&coverMediaItemID, &mediaItemsCount) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + slog.Error("error getting album mediaitem id and count", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - mediaItemCount := int(h.DB.Model(&album).Association("MediaItems").Count()) - album.MediaItemsCount = &mediaItemCount - album.CoverMediaItemID = &newCoverMediaItem.ID - if newCoverMediaItem.ID == uuid.Nil { - album.CoverMediaItemID = nil + _, err = atx.Exec(ctx.Request().Context(), queryUpdateAlbumMediaItems, mediaItemsCount, + coverMediaItemID, userID, uid) + if err != nil { + slog.Error("error updating album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - result := h.DB.Model(&album).Omit("MediaItems").Updates(map[string]interface{}{ - "MediaItemsCount": &mediaItemCount, - "CoverMediaItemID": album.CoverMediaItemID, - }) - if result.Error != nil { - slog.Error("error updating album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + if err = atx.Commit(ctx.Request().Context()); err != nil { + slog.Error("error committing transaction for removing album mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -127,18 +197,29 @@ func (h *Handler) GetAlbum(ctx echo.Context) error { if err != nil { return err } + album := models.Album{} - result := h.DB.Model(&models.Album{}). - Where("id=? AND user_id=?", uid, userID). - Preload("CoverMediaItem"). - First(&album) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + coverMediaItem := &models.CoverMediaItem{} + + err = h.DB.QueryRow(ctx.Request().Context(), queryGetAlbum, userID, uid).Scan(&album.ID, &album.UserID, + &album.Name, &album.Description, &album.IsShared, &album.IsHidden, &album.MediaItemsCount, + &album.CoverMediaItemID, &album.CreatedAt, &album.UpdatedAt, &coverMediaItem.ID, + &coverMediaItem.UserID, &coverMediaItem.SourceURL, &coverMediaItem.PreviewURL, &coverMediaItem.ThumbnailURL, + &coverMediaItem.Placeholder, &coverMediaItem.MediaItemType, &coverMediaItem.MediaItemCategory, + &coverMediaItem.Width, &coverMediaItem.Height) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "album not found") } - slog.Error("error getting album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + + if album.CoverMediaItemID != nil { + album.CoverMediaItem = coverMediaItem + } + return ctx.JSON(http.StatusOK, album) } @@ -155,11 +236,14 @@ func (h *Handler) UpdateAlbum(ctx echo.Context) error { } album.ID = uid album.UserID = userID - result := h.DB.Model(&album).Updates(album) - if result.Error != nil { - slog.Error("error updating album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + album.UpdatedAt = time.Now() + _, err = h.DB.Exec(ctx.Request().Context(), queryUpdateAlbum, userID, uid, album.Name, album.Description, album.IsShared, album.IsHidden, album.CoverMediaItemID, album.UpdatedAt) + if err != nil { + slog.Error("error updating album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -170,16 +254,13 @@ func (h *Handler) DeleteAlbum(ctx echo.Context) error { if err != nil { return err } - album := models.Album{ID: uid, UserID: userID} - err = h.DB.Model(&album).Association("MediaItems").Clear() + _, err = h.DB.Exec(ctx.Request().Context(), queryDeleteAlbum, userID, uid) if err != nil { slog.Error("error deleting album", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - if result := h.DB.Delete(&album); result.Error != nil { - slog.Error("error deleting album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) - } + return ctx.JSON(http.StatusNoContent, nil) } @@ -190,17 +271,23 @@ func (h *Handler) GetAlbums(ctx echo.Context) error { shared := getAlbumShared(ctx) order := getAlbumSortOrder(ctx) albums := []models.Album{} - result := h.DB.Model(&models.Album{}). - Where("is_hidden=false AND is_shared=? AND user_id=?", shared, userID). - Preload("CoverMediaItem"). - Order(order). - Find(&albums). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting albums", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), fmt.Sprintf(queryGetAlbums, order), userID, shared, offset, limit) + if err != nil { + slog.Error("error getting albums", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + album, err := models.ScanRowsToAlbum(rows) + if err != nil { + slog.Error("error scanning album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + albums = append(albums, album) } + return ctx.JSON(http.StatusOK, albums) } @@ -213,10 +300,16 @@ func (h *Handler) CreateAlbum(ctx echo.Context) error { } album.ID = uuid.NewV4() album.UserID = userID - if result := h.DB.Create(&album); result.Error != nil { - slog.Error("error creating album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + album.CreatedAt = time.Now() + album.UpdatedAt = album.CreatedAt + _, err = h.DB.Exec(ctx.Request().Context(), queryCreateAlbum, album.ID, album.UserID, album.Name, + album.Description, album.IsShared, album.IsHidden, album.CreatedAt, album.UpdatedAt) + if err != nil { + slog.Error("error creating album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusCreated, album) } @@ -225,8 +318,10 @@ func getAlbumID(ctx echo.Context) (uuid.UUID, error) { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting album id", "error", err) + return uuid.Nil, echo.NewHTTPError(http.StatusBadRequest, "invalid album id") } + return uid, err } @@ -235,6 +330,7 @@ func getMediaItems(ctx echo.Context) ([]*models.MediaItem, error) { err := ctx.Bind(mediaItemsRequest) if err != nil || len(mediaItemsRequest.MediaItems) == 0 { slog.Error("error getting album mediaitems", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitems") } mediaItems := make([]*models.MediaItem, len(mediaItemsRequest.MediaItems)) @@ -242,10 +338,12 @@ func getMediaItems(ctx echo.Context) ([]*models.MediaItem, error) { uid, err := uuid.FromString(mediaItem) if err != nil { slog.Error("error getting album mediaitem id", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } mediaItems[idx] = &models.MediaItem{ID: uid} } + return mediaItems, nil } @@ -254,12 +352,15 @@ func getAlbum(ctx echo.Context) (*models.Album, error) { err := ctx.Bind(albumRequest) if err != nil { slog.Error("error getting album", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid album") } - album := models.Album{ - Description: albumRequest.Description, - IsShared: albumRequest.IsShared, - IsHidden: albumRequest.IsHidden, + album := models.Album{Description: albumRequest.Description} + if albumRequest.IsShared != nil { + album.IsShared = albumRequest.IsShared + } + if albumRequest.IsHidden != nil { + album.IsHidden = albumRequest.IsHidden } if albumRequest.Name != nil { album.Name = *albumRequest.Name @@ -274,5 +375,6 @@ func getAlbum(ctx echo.Context) (*models.Album, error) { if reflect.DeepEqual(models.Album{}, album) { return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid album") } + return &album, nil } diff --git a/api/internal/handlers/albums_test.go b/api/internal/handlers/albums_test.go index 8032d7a0..37cffe52 100644 --- a/api/internal/handlers/albums_test.go +++ b/api/internal/handlers/albums_test.go @@ -7,137 +7,88 @@ import ( "strings" "testing" - "github.com/DATA-DOG/go-sqlmock" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" + uuid "github.com/satori/go.uuid" ) var ( + sampleName = "name" + sampleCoverMediaItemID = uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179") + sampleMediaItemsCount = 12 + albumCols = []string{ - "id", "user_id", "name", "description", "is_shared", "is_hidden", "cover_mediaitem_id", - "mediaitems_count", "created_at", "updated_at", + "id", "user_id", "name", "description", "is_shared", "is_hidden", "mediaitems_count", "cover_mediaitem_id", "created_at", "updated_at", } albumResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"name":"name","description":"description",` + `"shared":true,"hidden":false,"mediaItemsCount":12,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + `"coverMediaItem":{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + - `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + - `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + - `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + - `"updatedAt":"2022-09-22T11:22:33+05:30"}}` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","sourceUrl":"source_url","previewUrl":"preview_url",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder",` + + `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480}}` albumsResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"name":"name","description":"description",` + `"shared":true,"hidden":false,"mediaItemsCount":12,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + `"coverMediaItem":{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + - `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + - `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + - `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + - `"updatedAt":"2022-09-22T11:22:33+05:30"}},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","sourceUrl":"source_url","previewUrl":"preview_url",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","mediaItemType":"mediaitem_type",` + + `"mediaItemCategory":"mediaitem_category","width":720,"height":480}},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","name":"name",` + - `"description":"description","shared":false,"hidden":true,"mediaItemsCount":24,` + + `"description":"description","shared":false,"hidden":true,"mediaItemsCount":12,` + `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30","coverMediaItem":{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"filename":"filename","description":"description","mimeType":"mime_type","sourceUrl":"source_url",` + - `"previewUrl":"preview_url","thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,` + - `"status":"status","mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,` + - `"creationTime":"2022-09-22T11:22:33+05:30","cameraMake":"camera_make","cameraModel":"camera_model",` + - `"focalLength":"focal_length","apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent",` + - `"exposureTime":"exposure_time","latitude":17.580249,"longitude":-70.278493,"fps":"fps",` + - `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30"}}]` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","sourceUrl":"source_url","previewUrl":"preview_url",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","mediaItemType":"mediaitem_type",` + + `"mediaItemCategory":"mediaitem_category","width":720,"height":480}}]` ) func TestGetAlbumMediaItems(t *testing.T) { tests := []Test{ { - "get album mediaitems bad request", - http.MethodGet, - "/v1/albums/:id/mediaItems", - "/v1/albums/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album mediaitems bad request", http.MethodGet, "/v1/albums/:id/mediaItems", "/v1/albums/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbumMediaItems - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "get album mediaitems not found", - http.MethodGet, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album mediaitems not found", http.MethodGet, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbumMediaItems - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get album mediaitems with 2 rows", - http.MethodGet, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album mediaitems with error", http.MethodGet, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbumMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get album mediaitems with error", - http.MethodGet, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album mediaitems with error in scanning", http.MethodGet, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetAlbumMediaItems + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get album mediaitems with 2 rows", http.MethodGet, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -146,158 +97,118 @@ func TestGetAlbumMediaItems(t *testing.T) { func TestAddAlbumMediaItems(t *testing.T) { tests := []Test{ { - "add album mediaitems bad request", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "add album mediaitems bad request", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "add album mediaitems with bad payload", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "add album mediaitems with bad payload", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", + }, http.StatusBadRequest, "invalid mediaitems", }, { - "add album mediaitems with bad mediaitem", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "add album mediaitems with bad mediaitem", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "add album mediaitems with success", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "add album mediaitems with error starting transaction", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "mediaitems" JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow("1")) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}).WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "add album mediaitems with error", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "add album mediaitems with error adding album mediaitems", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "add album mediaitems with error updating album", - http.MethodPost, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "add album mediaitems with error getting album mediaitem id and count", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "mediaitems" JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow("1")) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.AddAlbumMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "add album mediaitems with error updating album", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", + }, + { + "add album mediaitems with error committing transaction", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit().WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.AddAlbumMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "add album mediaitems with success", http.MethodPost, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit() + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.AddAlbumMediaItems + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -306,218 +217,118 @@ func TestAddAlbumMediaItems(t *testing.T) { func TestRemoveAlbumMediaItems(t *testing.T) { tests := []Test{ { - "remove album mediaitems bad request", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "remove album mediaitems bad request", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "remove album mediaitems with bad payload", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with bad payload", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", + }, http.StatusBadRequest, "invalid mediaitems", }, { - "remove album mediaitems with bad mediaitem", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with bad mediaitem", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "remove album mediaitems with success", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with error starting transaction", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "mediaitems" JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow("1")) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}).WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "remove album mediaitems with error", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with error removing album mediaitems", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "remove album mediaitems with success and no cover mediaitem", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with error getting album mediaitem id and count", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "mediaitems" JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow("1")) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "remove album mediaitems with error getting cover mediaitem", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with error updating album", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "remove album mediaitems with error updating album", - http.MethodDelete, - "/v1/albums/:id/mediaItems", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "remove album mediaitems with error committing transaction", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit().WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.RemoveAlbumMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "remove album mediaitems with success", http.MethodDelete, "/v1/albums/:id/mediaItems", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT mediaitem_id, COUNT(*) OVER() AS mediaitems_count FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"mediaitem_id", "mediaitems_count"}). + AddRow(&sampleCoverMediaItemID, sampleMediaItemsCount)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "mediaitems" JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow("1")) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -526,87 +337,50 @@ func TestRemoveAlbumMediaItems(t *testing.T) { func TestGetAlbum(t *testing.T) { tests := []Test{ { - "get album bad request", - http.MethodGet, - "/v1/albums/:id", - "/v1/albums/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album bad request", http.MethodGet, "/v1/albums/:id", "/v1/albums/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbum - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "get album not found", - http.MethodGet, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(sqlmock.NewRows(albumCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album not found", http.MethodGet, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbum - }, - http.StatusNotFound, - "album not found", + }, http.StatusNotFound, "album not found", }, { - "get album", - http.MethodGet, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album with error", http.MethodGet, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbum - }, - http.StatusOK, - albumResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get album with error", - http.MethodGet, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get album with error in scanning", http.MethodGet, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetAlbum + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get album with success", http.MethodGet, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedAlbumRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, albumResponseBody, }, } executeTests(t, tests) @@ -615,136 +389,52 @@ func TestGetAlbum(t *testing.T) { func TestUpdateAlbum(t *testing.T) { tests := []Test{ { - "update album bad request", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update album bad request", http.MethodPut, "/v1/albums/:id", "/v1/albums/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "update album with no payload", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update album with no payload", http.MethodPut, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusBadRequest, - "invalid album", + }, http.StatusBadRequest, "invalid album", }, { - "update album with bad payload", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update album with bad payload", http.MethodPut, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusBadRequest, - "invalid album", + }, http.StatusBadRequest, "invalid album", }, { - "update album with bad cover mediaitem id", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update album with bad cover mediaitem id", http.MethodPut, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"bad-mediaitem-id"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"bad-mediaitem-id"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusBadRequest, - "invalid album cover mediaitem id", + }, http.StatusBadRequest, "invalid album cover mediaitem id", }, { - "update album with success", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update album with error", http.MethodPut, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","shared":true,"hidden":true,` + - `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "description", true, true, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description","shared":true,"hidden":true,` + + `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, sampleName, &sampleDescription, &sampleBoolTrue, &sampleBoolTrue, &sampleCoverMediaItemID, pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "update album with error", - http.MethodPut, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update album with success", http.MethodPut, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","shared":true,"hidden":true,` + - `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "description", true, true, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description","shared":true,"hidden":true,` + + `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, sampleName, &sampleDescription, &sampleBoolTrue, &sampleBoolTrue, &sampleCoverMediaItemID, pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -753,104 +443,27 @@ func TestUpdateAlbum(t *testing.T) { func TestDeleteAlbum(t *testing.T) { tests := []Test{ { - "delete album bad request", - http.MethodDelete, - "/v1/albums/:id", - "/v1/albums/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete album bad request", http.MethodDelete, "/v1/albums/:id", "/v1/albums/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteAlbum - }, - http.StatusBadRequest, - "invalid album id", + }, http.StatusBadRequest, "invalid album id", }, { - "delete album with success", - http.MethodDelete, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "albums"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.DeleteAlbum - }, - http.StatusNoContent, - "", - }, - { - "delete album with error while clearing linked mediaitems", - http.MethodDelete, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). + "delete album with error", http.MethodDelete, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "delete album with error", - http.MethodDelete, - "/v1/albums/:id", - "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "album_mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "albums"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete album with success", http.MethodDelete, "/v1/albums/:id", "/v1/albums/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -859,69 +472,45 @@ func TestDeleteAlbum(t *testing.T) { func TestGetAlbums(t *testing.T) { tests := []Test{ { - "get albums with empty table", - http.MethodGet, - "/v1/albums", - "/v1/albums?sort=name&shared=true", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(sqlmock.NewRows(albumCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get albums with empty table", http.MethodGet, "/v1/albums", "/v1/albums?sort=name&shared=true", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbums - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get albums with 2 rows", - http.MethodGet, - "/v1/albums", - "/v1/albums?sort=updatedAt", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get albums with error", http.MethodGet, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbums - }, - http.StatusOK, - albumsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get albums with error", - http.MethodGet, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get albums with error in scanning", http.MethodGet, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetAlbums - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get albums with 2 rows", http.MethodGet, "/v1/albums", "/v1/albums?sort=updatedAt", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedAlbumRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetAlbums + }, http.StatusOK, albumsResponseBody, }, } executeTests(t, tests) @@ -930,132 +519,57 @@ func TestGetAlbums(t *testing.T) { func TestCreateAlbum(t *testing.T) { tests := []Test{ { - "create album with bad payload", - http.MethodPost, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{ + "create album with bad payload", http.MethodPost, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateAlbum - }, - http.StatusBadRequest, - "invalid album", + }, http.StatusBadRequest, "invalid album", }, { - "create album with no payload", - http.MethodPost, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "create album with no payload", http.MethodPost, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateAlbum - }, - http.StatusBadRequest, - "invalid album", + }, http.StatusBadRequest, "invalid album", }, { - "create album with bad cover mediaitem id", - http.MethodPost, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{ + "create album with bad cover mediaitem id", http.MethodPost, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"bad-mediaitem-id"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"bad-mediaitem-id"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateAlbum - }, - http.StatusBadRequest, - "invalid album cover mediaitem id", + }, http.StatusBadRequest, "invalid album cover mediaitem id", }, { - "create album with success", - http.MethodPost, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{ + "create album with error", http.MethodPost, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "albums"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "name", "description", false, false, 0, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), "name", &sampleDescription, pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateAlbum - }, - http.StatusCreated, - `"name":"name","description":"description","shared":false,` + - `"hidden":false,"mediaItemsCount":0,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",`, + }, http.StatusInternalServerError, "some db error", }, { - "create album with error", - http.MethodPost, - "/v1/albums", - "/v1/albums", - []string{}, - []string{}, - map[string]string{ + "create album with success", http.MethodPost, "/v1/albums", "/v1/albums", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "albums"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "name", "description", false, false, 0, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","description":"description","coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), "name", &sampleDescription, pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusCreated, `"name":"name","description":"description",`, }, } executeTests(t, tests) } -func getMockedAlbumRow() *sqlmock.Rows { - return sqlmock.NewRows(albumCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "description", - "true", "false", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "12", sampleTime, sampleTime) +func getMockedAlbumRow() *pgxmock.Rows { + return pgxmock.NewRows(append(albumCols, coverMediaItemCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight) } -func getMockedAlbumRows() *sqlmock.Rows { - return sqlmock.NewRows(albumCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "description", - "true", "false", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "12", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "description", - "false", "true", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "24", sampleTime, sampleTime) +func getMockedAlbumRows() *pgxmock.Rows { + return pgxmock.NewRows(append(albumCols, coverMediaItemCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolFalse, &sampleBoolTrue, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight) } diff --git a/api/internal/handlers/auth.go b/api/internal/handlers/auth.go index 838d7710..0cdfa19a 100644 --- a/api/internal/handlers/auth.go +++ b/api/internal/handlers/auth.go @@ -4,16 +4,15 @@ import ( "api/internal/auth" "api/internal/models" "errors" + "log/slog" "net/http" "strings" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) -type ( - // LoginRequest ... +type ( // LoginRequest ... LoginRequest struct { Username *string `json:"username"` Password *string `json:"password"` @@ -26,6 +25,10 @@ type ( } ) +const ( + queryAuthLogin = `SELECT * FROM users WHERE username=$1 AND password=$2` +) + // Login ... func (h *Handler) Login(ctx echo.Context) error { loginRequest, err := getUsernameAndPassword(ctx) @@ -33,25 +36,24 @@ func (h *Handler) Login(ctx echo.Context) error { return err } user := models.User{} - result := h.DB.Model(&models.User{}). - Where("username=? AND password=?", &loginRequest.Username, getPasswordHash(*loginRequest.Password)). - First(&user) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + err = h.DB.QueryRow(ctx.Request().Context(), queryAuthLogin, *loginRequest.Username, *loginRequest.Password). + Scan(&user.ID, &user.Name, &user.Username, &user.Password, &user.Features, &user.CreatedAt, &user.UpdatedAt) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "incorrect username or password") } - slog.Error("error getting user", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } accessToken, refreshToken, err := auth.GetTokens(h.Config, h.Cache, user) if err != nil { slog.Error("error getting tokens", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, "error getting tokens") } - authResponse := AuthResponse{ - AccessToken: accessToken, - RefreshToken: refreshToken, - } + authResponse := AuthResponse{AccessToken: accessToken, RefreshToken: refreshToken} + return ctx.JSON(http.StatusOK, authResponse) } @@ -62,12 +64,11 @@ func (h *Handler) Refresh(ctx echo.Context) error { newAccessToken, newRefreshToken, err := auth.RefreshTokens(h.Config, h.Cache, refreshToken) if err != nil { slog.Error("error refreshing tokens", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, "error refreshing tokens") } - authResponse := AuthResponse{ - AccessToken: newAccessToken, - RefreshToken: newRefreshToken, - } + authResponse := AuthResponse{AccessToken: newAccessToken, RefreshToken: newRefreshToken} + return ctx.JSON(http.StatusOK, authResponse) } @@ -76,6 +77,7 @@ func (h *Handler) Logout(ctx echo.Context) error { accessToken := ctx.Request().Header.Get("Authorization") accessToken = strings.ReplaceAll(accessToken, "Bearer ", "") _ = auth.RemoveTokens(h.Cache, accessToken) + return ctx.JSON(http.StatusNoContent, nil) } @@ -84,11 +86,16 @@ func getUsernameAndPassword(ctx echo.Context) (*LoginRequest, error) { err := ctx.Bind(loginRequest) if err != nil { slog.Error("error getting username and password", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid username or password") } if loginRequest.Username == nil || loginRequest.Password == nil { slog.Error("error getting username and password", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid username or password") } + + *loginRequest.Password = getPasswordHash(*loginRequest.Password) + return loginRequest, nil } diff --git a/api/internal/handlers/auth_test.go b/api/internal/handlers/auth_test.go index c6a891e8..58029a26 100644 --- a/api/internal/handlers/auth_test.go +++ b/api/internal/handlers/auth_test.go @@ -1,255 +1,129 @@ package handlers import ( + "api/config" + "api/internal/auth" + "api/internal/models" "errors" "net/http" "regexp" "strings" "testing" - "api/config" - "api/internal/auth" - "api/internal/models" - - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" uuid "github.com/satori/go.uuid" ) func TestLogin(t *testing.T) { tests := []Test{ { - "login with bad payload", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with bad payload", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusBadRequest, - "invalid username or password", + }, http.StatusBadRequest, "invalid username or password", }, { - "login with no payload", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "login with no payload", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusBadRequest, - "invalid username or password", + }, http.StatusBadRequest, "invalid username or password", }, { - "login with incomplete credentials", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with incomplete credentials", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"username":"username"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"username":"username"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusBadRequest, - "invalid username or password", + }, http.StatusBadRequest, "invalid username or password", }, { - "login with success", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with success", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). + }, strings.NewReader(`{"username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs("username", pgxmock.AnyArg()). WillReturnRows(getMockedUserRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusOK, - `"accessToken"`, + }, http.StatusOK, `"accessToken"`, }, { - "login with no user found", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with no user found", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(sqlmock.NewRows(userCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs("username", pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(userCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusNotFound, - "incorrect username or password", + }, http.StatusNotFound, "incorrect username or password", }, { - "login with error", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with error", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). + }, strings.NewReader(`{"username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs("username", pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "login with error getting tokens", - http.MethodPost, - "/v1/auth/login", - "/v1/auth/login", - []string{}, - []string{}, - map[string]string{ + "login with error getting tokens", http.MethodPost, "/v1/auth/login", "/v1/auth/login", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). + }, strings.NewReader(`{"username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs("username", pgxmock.AnyArg()). WillReturnRows(getMockedUserRow()) - }, - []func(interface{}, interface{}) (interface{}, error){ + }, []func(interface{}, interface{}) (interface{}, error){ func(a interface{}, b interface{}) (interface{}, error) { val, ok := b.(bool) if ok && val == true { return b, nil } return nil, errors.New("some cache error") - }, - nil, - }, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, + }, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Login - }, - http.StatusInternalServerError, - "error getting tokens", + }, http.StatusInternalServerError, "error getting tokens", }, } executeTests(t, tests) } func TestRefresh(t *testing.T) { - _, rtoken := auth.GetAccessAndRefreshTokens(&config.Config{Auth: config.Auth{RefreshTTL: 60}}, - models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username"}) + _, rtoken := auth.GetAccessAndRefreshTokens(&config.Config{Auth: config.Auth{RefreshTTL: 60}}, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username", + }) tests := []Test{ { - "refresh with success", - http.MethodPost, - "/v1/auth/refresh", - "/v1/auth/refresh", - []string{}, - []string{}, - map[string]string{ + "refresh with success", http.MethodPost, "/v1/auth/refresh", "/v1/auth/refresh", []string{}, []string{}, map[string]string{ echo.HeaderAuthorization: rtoken, - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Refresh - }, - http.StatusOK, - `"accessToken"`, + }, http.StatusOK, `"accessToken"`, }, { - "refresh with error", - http.MethodPost, - "/v1/auth/refresh", - "/v1/auth/refresh", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "refresh with error", http.MethodPost, "/v1/auth/refresh", "/v1/auth/refresh", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Refresh - }, - http.StatusInternalServerError, - "error refreshing tokens", + }, http.StatusInternalServerError, "error refreshing tokens", }, } executeTests(t, tests) } func TestLogout(t *testing.T) { - _, atoken := auth.GetAccessAndRefreshTokens(&config.Config{Auth: config.Auth{RefreshTTL: 60}}, - models.User{ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username"}) + _, atoken := auth.GetAccessAndRefreshTokens(&config.Config{Auth: config.Auth{RefreshTTL: 60}}, models.User{ + ID: uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179"), Username: "username", + }) tests := []Test{ { - "logout with success", - http.MethodPost, - "/v1/auth/logout", - "/v1/auth/logout", - []string{}, - []string{}, - map[string]string{ + "logout with success", http.MethodPost, "/v1/auth/logout", "/v1/auth/logout", []string{}, []string{}, map[string]string{ echo.HeaderAuthorization: atoken, - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Logout - }, - http.StatusNoContent, - ``, + }, http.StatusNoContent, ``, }, } executeTests(t, tests) diff --git a/api/internal/handlers/default.go b/api/internal/handlers/default.go index 1b7e8073..7da7b9e2 100644 --- a/api/internal/handlers/default.go +++ b/api/internal/handlers/default.go @@ -3,11 +3,11 @@ package handlers import ( "api/internal/models" "api/pkg/services/worker" + "log/slog" "net/http" "github.com/labstack/echo/v4" "github.com/pgvector/pgvector-go" - "golang.org/x/exp/slog" ) const ( @@ -18,12 +18,11 @@ const ( // GetVersion ... func (h *Handler) GetVersion(ctx echo.Context) error { version := models.GetVersion() + return ctx.JSON(http.StatusOK, version) } // GetFeatures ... -// -//nolint:cyclop func (h *Handler) GetFeatures(ctx echo.Context) error { cfgFeatures := models.GetFeatures(h.Config) features, _ := ctx.Get("features").(models.Features) @@ -34,7 +33,6 @@ func (h *Handler) GetFeatures(ctx echo.Context) error { features.Albums = features.Albums && cfgFeatures.Albums features.Explore = features.Explore && cfgFeatures.Explore features.Places = features.Places && cfgFeatures.Places - features.Things = features.Things && cfgFeatures.Things features.People = features.People && cfgFeatures.People features.Sharing = features.Sharing && cfgFeatures.Sharing features.Jobs = features.Jobs && cfgFeatures.Jobs @@ -45,6 +43,7 @@ func (h *Handler) GetFeatures(ctx echo.Context) error { // GetDisk ... func (h *Handler) GetDisk(ctx echo.Context) error { disk := models.GetDisk(h.Config) + return ctx.JSON(http.StatusOK, disk) } @@ -55,25 +54,52 @@ func (h *Handler) Search(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "invalid search query") } mediaItems := []models.MediaItem{} - if h.Config.ML.Search { + if h.Config.Search { searchEmbedding, err := h.Worker.GenerateEmbedding(ctx.Request().Context(), &worker.GenerateEmbeddingRequest{Text: searchQuery}) if err != nil { slog.Error("error getting search query embedding", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + rows, err := h.DB.Query(ctx.Request().Context(), + "SELECT * FROM mediaitems WHERE id IN (SELECT id from mediaitem_embeddings ORDER BY embedding <-> $1) LIMIT $2", + pgvector.NewVector(searchEmbedding.Embedding), searchDefaultLimit) + if err != nil { + slog.Error("error searching mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - result := h.DB.Raw("SELECT * FROM mediaitems WHERE id IN (SELECT id from mediaitem_embeddings ORDER BY embedding <-> ?)", pgvector.NewVector(searchEmbedding.Embedding)). - Find(&mediaItems).Limit(searchDefaultLimit) - if result.Error != nil { - slog.Error("error searching mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning album mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) } + return ctx.JSON(http.StatusOK, mediaItems) } - result := h.DB.Raw("SELECT * FROM mediaitems WHERE to_tsvector('english', keywords) @@ plainto_tsquery('english', ?)", searchQuery). - Find(&mediaItems).Limit(searchDefaultLimit) - if result.Error != nil { - slog.Error("error searching mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), + "SELECT * FROM mediaitems WHERE to_tsvector('english', caption) @@ plainto_tsquery('english', $1) LIMIT $2", + searchQuery, searchDefaultLimit) + if err != nil { + slog.Error("error searching mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) } + return ctx.JSON(http.StatusOK, mediaItems) } diff --git a/api/internal/handlers/default_test.go b/api/internal/handlers/default_test.go index 0dbe5ed3..754d8973 100644 --- a/api/internal/handlers/default_test.go +++ b/api/internal/handlers/default_test.go @@ -6,49 +6,23 @@ import ( "regexp" "testing" - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) func TestGetFeatures(t *testing.T) { tests := []Test{ { - "get features with error", - http.MethodGet, - "/v1/features", - "/v1/features", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get features with error", http.MethodGet, "/v1/features", "/v1/features", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetFeatures - }, - http.StatusOK, - "{}", + }, http.StatusOK, "{}", }, { - "get features successfully", - http.MethodGet, - "/v1/features", - "/v1/features", - []string{}, - []string{}, - map[string]string{ + "get features successfully", http.MethodGet, "/v1/features", "/v1/features", []string{}, []string{}, map[string]string{ echo.HeaderAuthorization: "atoken", - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetFeatures - }, - http.StatusOK, - `{"albums":true,"explore":true,"places":true,"things":true,"people":true}`, + }, http.StatusOK, `{"albums":true,"explore":true,"places":true,"people":true}`, }, } executeTests(t, tests) @@ -57,22 +31,9 @@ func TestGetFeatures(t *testing.T) { func TestGetVersion(t *testing.T) { tests := []Test{ { - "get version successfully", - http.MethodGet, - "/v1/version", - "/v1/version", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get version successfully", http.MethodGet, "/version", "/version", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetVersion - }, - http.StatusOK, - ``, + }, http.StatusOK, ``, }, } executeTests(t, tests) @@ -81,22 +42,9 @@ func TestGetVersion(t *testing.T) { func TestGetDisk(t *testing.T) { tests := []Test{ { - "get disk successfully", - http.MethodGet, - "/v1/disk", - "/v1/disk", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get disk successfully", http.MethodGet, "/disk", "/disk", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetDisk - }, - http.StatusOK, - ``, + }, http.StatusOK, ``, }, } executeTests(t, tests) @@ -105,103 +53,50 @@ func TestGetDisk(t *testing.T) { func TestSearch(t *testing.T) { tests := []Test{ { - "search mediaitems with bad request", - http.MethodGet, - "/v1/search", - "/v1/search", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "search mediaitems with bad request", http.MethodGet, "/v1/search", "/v1/search", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.Search - }, - http.StatusBadRequest, - "invalid search query", + }, http.StatusBadRequest, "invalid search query", }, { - "search mediaitems with no results", - http.MethodGet, - "/v1/search", - "/v1/search?q=keyword", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { + "search mediaitems with no results", http.MethodGet, "/v1/search", "/v1/search?q=keyword", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - &mockWorkerGRPCClient{wantOk: true}, - func(handler *Handler) func(ctx echo.Context) error { + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, &mockWorkerGRPCClient{wantOk: true}, func(handler *Handler) func(ctx echo.Context) error { return handler.Search - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "search mediaitems with 2 rows", - http.MethodGet, - "/v1/search", - "/v1/search?q=keyword", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - &mockWorkerGRPCClient{wantOk: true}, - func(handler *Handler) func(ctx echo.Context) error { + "search mediaitems with error getting embedding", http.MethodGet, "/v1/search", "/v1/search?q=keyword", []string{}, []string{}, map[string]string{}, nil, nil, nil, &mockWorkerGRPCClient{wantErr: true}, func(handler *Handler) func(ctx echo.Context) error { return handler.Search - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusInternalServerError, "some grpc error", }, { - "search mediaitems with error", - http.MethodGet, - "/v1/search", - "/v1/search?q=keyword", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { + "search mediaitems with error", http.MethodGet, "/v1/search", "/v1/search?q=keyword", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - &mockWorkerGRPCClient{wantOk: true}, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, &mockWorkerGRPCClient{wantOk: true}, func(handler *Handler) func(ctx echo.Context) error { + return handler.Search + }, http.StatusInternalServerError, "some db error", + }, + { + "search mediaitems with error in scanning", http.MethodGet, "/v1/search", "/v1/search?q=keyword", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, &mockWorkerGRPCClient{wantOk: true}, func(handler *Handler) func(ctx echo.Context) error { return handler.Search - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", }, { - "search mediaitems with error getting embedding", - http.MethodGet, - "/v1/search", - "/v1/search?q=keyword", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - &mockWorkerGRPCClient{wantErr: true}, - func(handler *Handler) func(ctx echo.Context) error { + "search mediaitems with 2 rows", http.MethodGet, "/v1/search", "/v1/search?q=keyword", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, &mockWorkerGRPCClient{wantOk: true}, func(handler *Handler) func(ctx echo.Context) error { return handler.Search - }, - http.StatusInternalServerError, - "some grpc error", + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) diff --git a/api/internal/handlers/explore.go b/api/internal/handlers/explore.go index b1ea0e7c..eab3e860 100644 --- a/api/internal/handlers/explore.go +++ b/api/internal/handlers/explore.go @@ -3,17 +3,17 @@ package handlers import ( "api/internal/models" "errors" + "log/slog" "net/http" "reflect" + "time" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) -type ( - // PeopleRequest ... +type ( // PeopleRequest ... PeopleRequest struct { Name *string `json:"name"` IsHidden *bool `json:"hidden"` @@ -23,28 +23,70 @@ type ( // MemoryMediaItem ... MemoryMediaItem struct { models.MediaItem - Year string `json:"year" gorm:"column:creation_year"` + + Year string `json:"year"` } ) +const ( + queryGetYearsAgoMediaItems = `SELECT *, EXTRACT(year FROM creation_time) as creation_year FROM mediaitems WHERE user_id=$1 AND` + + ` EXTRACT(month FROM creation_time)=$2 AND EXTRACT(day FROM creation_time)=$3 AND EXTRACT(year FROM creation_time)` + + ` IN (SELECT EXTRACT(year FROM creation_time) FROM mediaitems) ORDER BY creation_time` + queryGetPlace = `SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems m` + + ` ON p.cover_mediaitem_id=m.id WHERE p.user_id=$1 AND p.id=$2` + queryGetPlaces = `SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems m` + + ` ON p.cover_mediaitem_id=m.id WHERE p.user_id=$1 AND p.is_hidden=false ORDER BY p.created_at DESC` + + ` OFFSET $2 LIMIT $3` + queryGetPlaceMediaItems = `SELECT * FROM mediaitems WHERE id IN (SELECT mediaitem_id FROM place_mediaitems` + + ` WHERE user_id=$1 AND place_id=$2) AND is_hidden=false ORDER BY created_at DESC OFFSET $3 LIMIT $4` + queryGetPerson = `SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces mf ON p.cover_mediaitem_face_id=mf.id` + + ` WHERE p.user_id=$1 AND p.id=$2` + queryGetPeople = `SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces mf ON p.cover_mediaitem_face_id=mf.id` + + ` WHERE p.user_id=$1 AND p.is_hidden=false ORDER BY p.created_at DESC OFFSET $2 LIMIT $3` + queryGetPersonMediaItems = `SELECT * FROM mediaitems WHERE id IN (SELECT mediaitem_id FROM people_mediaitems` + + ` WHERE user_id=$1 AND people_id=$2) AND is_hidden=false ORDER BY created_at DESC OFFSET $3 LIMIT $4` + queryUpdatePerson = `UPDATE people SET name=$3, is_hidden=$4, cover_mediaitem_id=$5, cover_mediaitem_face_id=$6,` + + ` updated_at=$7 WHERE user_id=$1 AND id=$2` +) + // GetYearsAgoMediaItems ... func (h *Handler) GetYearsAgoMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) month, date, err := getMonthAndDate(ctx) if err != nil { slog.Error("error getting month and date", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid month and date") } var memoryMediaItems []MemoryMediaItem - result := h.DB.Raw("SELECT *, EXTRACT(year FROM creation_time) as creation_year "+ - "FROM mediaitems "+ - "WHERE user_id = ? AND EXTRACT(month FROM creation_time) = ? AND EXTRACT(day FROM creation_time) = ? "+ - "AND EXTRACT(year FROM creation_time) IN (SELECT EXTRACT(year FROM creation_time) FROM mediaitems) "+ - "ORDER BY creation_time", userID, month, date).Scan(&memoryMediaItems) - if result.Error != nil { - slog.Error("error getting years ago mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetYearsAgoMediaItems, userID, month, date) + if err != nil { + slog.Error("error getting years ago mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + var memoryItem MemoryMediaItem + err = rows.Scan(&memoryItem.ID, &memoryItem.UserID, &memoryItem.Filename, &memoryItem.Hash, + &memoryItem.Description, &memoryItem.MimeType, &memoryItem.SourceURL, &memoryItem.PreviewURL, + &memoryItem.ThumbnailURL, &memoryItem.Placeholder, &memoryItem.IsFavourite, &memoryItem.IsHidden, + &memoryItem.IsDeleted, &memoryItem.Status, &memoryItem.MediaItemType, &memoryItem.MediaItemCategory, + &memoryItem.Width, &memoryItem.Height, &memoryItem.CreationTime, &memoryItem.CameraMake, + &memoryItem.CameraModel, &memoryItem.FocalLength, &memoryItem.ApertureFnumber, &memoryItem.IsoEquivalent, + &memoryItem.ExposureTime, &memoryItem.Megapixels, &memoryItem.Latitude, &memoryItem.Longitude, + &memoryItem.FPS, &memoryItem.EXIFData, &memoryItem.DetectedText, &memoryItem.Caption, &memoryItem.CreatedAt, + &memoryItem.UpdatedAt, &memoryItem.Year) + if err != nil { + slog.Error("error scanning years ago mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + memoryMediaItems = append(memoryMediaItems, memoryItem) } + return ctx.JSON(http.StatusOK, memoryMediaItems) } @@ -53,16 +95,23 @@ func (h *Handler) GetPlaces(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) places := []models.Place{} - result := h.DB.Model(&models.Place{}). - Where("user_id=? AND is_hidden=false", userID). - Preload("CoverMediaItem"). - Find(&places). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting places", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetPlaces, userID, offset, limit) + if err != nil { + slog.Error("error getting places", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + place, err := models.ScanRowsToPlace(rows) + if err != nil { + slog.Error("error scanning place", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + places = append(places, place) } + return ctx.JSON(http.StatusOK, places) } @@ -73,20 +122,31 @@ func (h *Handler) GetPlace(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting place id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid place id") } place := models.Place{} - result := h.DB.Model(&models.Place{}). - Where("id=? AND user_id=?", uid, userID). - Preload("CoverMediaItem"). - First(&place) - if result.Error != nil { - slog.Error("error getting place", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + coverMediaItem := &models.CoverMediaItem{} + + err = h.DB.QueryRow(ctx.Request().Context(), queryGetPlace, userID, uid).Scan(&place.ID, &place.UserID, + &place.Name, &place.Postcode, &place.Country, &place.Locality, &place.Area, &place.IsHidden, + &place.CoverMediaItemID, &place.CreatedAt, &place.UpdatedAt, &coverMediaItem.ID, + &coverMediaItem.UserID, &coverMediaItem.SourceURL, &coverMediaItem.PreviewURL, + &coverMediaItem.ThumbnailURL, &coverMediaItem.Placeholder, &coverMediaItem.MediaItemType, + &coverMediaItem.MediaItemCategory, &coverMediaItem.Width, &coverMediaItem.Height) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "place not found") } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting place", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + if place.CoverMediaItemID != nil { + place.CoverMediaItem = coverMediaItem } + return ctx.JSON(http.StatusOK, place) } @@ -98,81 +158,27 @@ func (h *Handler) GetPlaceMediaItems(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting place id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid place id") } - place := new(models.Place) - place.ID = uid - place.UserID = userID mediaItems := []models.MediaItem{} - err = h.DB.Model(&place).Offset(offset).Limit(limit).Association("MediaItems").Find(&mediaItems, "is_hidden=? AND is_deleted=?", false, false) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetPlaceMediaItems, userID, uid, offset, limit) if err != nil { slog.Error("error getting place mediaitems", "error", err) - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - return ctx.JSON(http.StatusOK, mediaItems) -} -// GetThings ... -func (h *Handler) GetThings(ctx echo.Context) error { - userID := getRequestingUserID(ctx) - offset, limit := getOffsetAndLimit(ctx) - things := []models.Thing{} - result := h.DB.Model(&models.Thing{}). - Where("user_id=? AND is_hidden=false", userID). - Preload("CoverMediaItem"). - Find(&things). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting things", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return ctx.JSON(http.StatusOK, things) -} + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning place mediaitem", "error", err) -// GetThing ... -func (h *Handler) GetThing(ctx echo.Context) error { - userID := getRequestingUserID(ctx) - id := ctx.Param("id") - uid, err := uuid.FromString(id) - if err != nil { - slog.Error("error getting thing id", "error", err) - return echo.NewHTTPError(http.StatusBadRequest, "invalid thing id") - } - thing := models.Thing{} - result := h.DB.Model(&models.Thing{}). - Where("id=? AND user_id=?", uid, userID). - Preload("CoverMediaItem"). - First(&thing) - if result.Error != nil { - slog.Error("error getting thing", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return echo.NewHTTPError(http.StatusNotFound, "thing not found") + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + mediaItems = append(mediaItems, mediaItem) } - return ctx.JSON(http.StatusOK, thing) -} -// GetThingMediaItems ... -func (h *Handler) GetThingMediaItems(ctx echo.Context) error { - userID := getRequestingUserID(ctx) - offset, limit := getOffsetAndLimit(ctx) - id := ctx.Param("id") - uid, err := uuid.FromString(id) - if err != nil { - slog.Error("error getting thing id", "error", err) - return echo.NewHTTPError(http.StatusBadRequest, "invalid thing id") - } - thing := new(models.Thing) - thing.ID = uid - thing.UserID = userID - mediaItems := []models.MediaItem{} - err = h.DB.Model(&thing).Offset(offset).Limit(limit).Association("MediaItems").Find(&mediaItems, "is_hidden=? AND is_deleted=?", false, false) - if err != nil { - slog.Error("error getting thing mediaitems", "error", err) - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } return ctx.JSON(http.StatusOK, mediaItems) } @@ -183,6 +189,7 @@ func (h *Handler) UpdatePerson(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting people id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid people id") } people, err := getPeople(ctx) @@ -191,11 +198,15 @@ func (h *Handler) UpdatePerson(ctx echo.Context) error { } people.ID = uid people.UserID = userID - result := h.DB.Model(&people).Updates(people) - if result.Error != nil { - slog.Error("error updating people", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + people.UpdatedAt = time.Now() + _, err = h.DB.Exec(ctx.Request().Context(), queryUpdatePerson, userID, uid, people.Name, people.IsHidden, + people.CoverMediaItemID, people.CoverMediaItemFaceID, people.UpdatedAt) + if err != nil { + slog.Error("error updating person", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -204,16 +215,23 @@ func (h *Handler) GetPeople(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) people := []models.People{} - result := h.DB.Model(&models.People{}). - Where("user_id=? AND is_hidden=false", userID). - Preload("CoverMediaItemFace"). - Find(&people). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting people", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetPeople, userID, offset, limit) + if err != nil { + slog.Error("error getting people", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + person, err := models.ScanRowsToPerson(rows) + if err != nil { + slog.Error("error scanning person", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + people = append(people, person) + } + return ctx.JSON(http.StatusOK, people) } @@ -224,42 +242,55 @@ func (h *Handler) GetPerson(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting person id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid person id") } - person := models.People{} - result := h.DB.Model(&models.People{}). - Where("id=? AND user_id=?", uid, userID). - Preload("CoverMediaItemFace"). - First(&person) - if result.Error != nil { - slog.Error("error getting person", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + person := models.People{CoverMediaItemFace: &models.MediaitemFace{}} + err = h.DB.QueryRow(ctx.Request().Context(), queryGetPerson, userID, uid).Scan(&person.ID, &person.UserID, + &person.Name, &person.IsHidden, &person.CoverMediaItemID, &person.CoverMediaItemFaceID, &person.CreatedAt, + &person.UpdatedAt, &person.CoverMediaItemFace.ID, &person.CoverMediaItemFace.MediaitemID, + &person.CoverMediaItemFace.PeopleID, &person.CoverMediaItemFace.Embedding, &person.CoverMediaItemFace.Thumbnail) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "person not found") } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting person", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusOK, person) } -// GetPeopleMediaItems ... -func (h *Handler) GetPeopleMediaItems(ctx echo.Context) error { +// GetPersonMediaItems ... +func (h *Handler) GetPersonMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) id := ctx.Param("id") uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting person id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid people id") } - person := new(models.People) - person.ID = uid - person.UserID = userID mediaItems := []models.MediaItem{} - err = h.DB.Model(&person).Offset(offset).Limit(limit).Association("MediaItems").Find(&mediaItems, "is_hidden=? AND is_deleted=?", false, false) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetPersonMediaItems, userID, uid, offset, limit) if err != nil { - slog.Error("error getting people mediaitems", "error", err) + slog.Error("error getting person mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning person mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) + } + return ctx.JSON(http.StatusOK, mediaItems) } @@ -268,11 +299,10 @@ func getPeople(ctx echo.Context) (*models.People, error) { err := ctx.Bind(peopleRequest) if err != nil { slog.Error("error getting people", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid people") } - people := models.People{ - IsHidden: peopleRequest.IsHidden, - } + people := models.People{IsHidden: peopleRequest.IsHidden} if peopleRequest.Name != nil { people.Name = *peopleRequest.Name } @@ -286,5 +316,6 @@ func getPeople(ctx echo.Context) (*models.People, error) { if reflect.DeepEqual(models.People{}, people) { return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid people") } + return &people, nil } diff --git a/api/internal/handlers/explore_test.go b/api/internal/handlers/explore_test.go index ba57f997..687dc796 100644 --- a/api/internal/handlers/explore_test.go +++ b/api/internal/handlers/explore_test.go @@ -7,17 +7,22 @@ import ( "strings" "testing" - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) var ( + samplePostCode = "postcode" + sampleCountry = "country" + sampleLocality = "locality" + sampleArea = "area" + placeCols = []string{ - "id", "user_id", "name", "postcode", "town", "city", "state", - "country", "cover_mediaitem_id", "is_hidden", "created_at", "updated_at", + "id", "user_id", "name", "postcode", "country", "locality", "area", "is_hidden", "cover_mediaitem_id", "created_at", "updated_at", + } + peopleCols = []string{ + "id", "user_id", "name", "is_hidden", "cover_mediaitem_id", "cover_mediaitem_face_id", "created_at", "updated_at", } - thingCols = []string{"id", "user_id", "name", "cover_mediaitem_id", "is_hidden", "created_at", "updated_at"} - peopleCols = []string{"id", "user_id", "name", "cover_mediaitem_id", "cover_mediaitem_face_id", "is_hidden", "created_at", "updated_at"} memoryMediaItemCols = append(mediaitemCols, "creation_year") memoryMediaItemsResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + @@ -26,7 +31,7 @@ var ( `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + + `"megapixels":"18.4","latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30","year":"2023"},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + @@ -34,48 +39,26 @@ var ( `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + + `"megapixels":"18.4","latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30","year":"2022"}]` coverMediaItemResponseBody = `"coverMediaItem":{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + - `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + - `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + - `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + - `"updatedAt":"2022-09-22T11:22:33+05:30"}` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","sourceUrl":"source_url","previewUrl":"preview_url",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","mediaItemType":"mediaitem_type",` + + `"mediaItemCategory":"mediaitem_category","width":720,"height":480}` coverFaceResponseBody = `"coverMediaItemFace":{"thumbnail":"thumbnail"}` placeResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"name":"name","postcode":"postcode",` + - `"town":"town","city":"city",` + - `"state":"state","country":"country","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + + `"name":"name","postcode":"postcode","country":"country","locality":"locality","area":"area","hidden":true,` + + `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + coverMediaItemResponseBody + `}` placesResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"name":"name","postcode":"postcode",` + - `"town":"town","city":"city",` + - `"state":"state","country":"country","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + + `"name":"name","postcode":"postcode","country":"country","locality":"locality","area":"area","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + coverMediaItemResponseBody + `},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","name":"name",` + - `"postcode":"postcode","town":"town","city":"city",` + - `"state":"state","country":"country","hidden":false,` + + `"postcode":"postcode","country":"country","locality":"locality","area":"area","hidden":false,` + `"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30",` + coverMediaItemResponseBody + `}]` - thingResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"name":"name",` + - `"hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + - coverMediaItemResponseBody + `}` - thingsResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"name":"name",` + - `"hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30",` + - coverMediaItemResponseBody + `},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + - `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","name":"name",` + - `"hidden":false,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","createdAt":"2022-09-22T11:22:33+05:30",` + - `"updatedAt":"2022-09-22T11:22:33+05:30",` + coverMediaItemResponseBody + `}]` personResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","name":"name",` + `"hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","coverMediaItemFaceId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + @@ -94,85 +77,45 @@ var ( func TestGetYearsAgoMediaItems(t *testing.T) { tests := []Test{ { - "get years ago mediaitems bad request", - http.MethodGet, - "/v1/explore/yearsAgo/:monthDate/mediaItems", - "/v1/explore/yearsAgo/bad-month-date/mediaItems", - []string{"monthDate"}, - []string{"bad-month-date"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get years ago mediaitems bad request", http.MethodGet, "/v1/explore/yearsAgo/:monthDate/mediaItems", "/v1/explore/yearsAgo/bad-month-date/mediaItems", []string{"monthDate"}, []string{"bad-month-date"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetYearsAgoMediaItems - }, - http.StatusBadRequest, - "invalid month and date", + }, http.StatusBadRequest, "invalid month and date", }, { - "get years ago mediaitems not found", - http.MethodGet, - "/v1/explore/yearsAgo/:monthDate/mediaItems", - "/v1/explore/yearsAgo/0403/mediaItems", - []string{"monthDate"}, - []string{"0403"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { + "get years ago mediaitems not found", http.MethodGet, "/v1/explore/yearsAgo/:monthDate/mediaItems", "/v1/explore/yearsAgo/0403/mediaItems", []string{"monthDate"}, []string{"0403"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { mock.ExpectQuery(regexp.QuoteMeta(`FROM mediaitems`)). - WillReturnRows(sqlmock.NewRows(memoryMediaItemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + WithArgs(pgxmock.AnyArg(), "04", "03"). + WillReturnRows(pgxmock.NewRows(memoryMediaItemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetYearsAgoMediaItems - }, - http.StatusOK, - "", + }, http.StatusOK, "", }, { - "get years ago mediaitems with 2 years", - http.MethodGet, - "/v1/explore/yearsAgo/:monthDate/mediaItems", - "/v1/explore/yearsAgo/0403/mediaItems", - []string{"monthDate"}, - []string{"0403"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { + "get years ago mediaitems with error", http.MethodGet, "/v1/explore/yearsAgo/:monthDate/mediaItems", "/v1/explore/yearsAgo/0403/mediaItems", []string{"monthDate"}, []string{"0403"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { mock.ExpectQuery(regexp.QuoteMeta(`FROM mediaitems`)). - WillReturnRows(getMockedMemoryMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + WithArgs(pgxmock.AnyArg(), "04", "03"). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetYearsAgoMediaItems - }, - http.StatusOK, - memoryMediaItemsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get years ago mediaitems with error", - http.MethodGet, - "/v1/explore/yearsAgo/:monthDate/mediaItems", - "/v1/explore/yearsAgo/0403/mediaItems", - []string{"monthDate"}, - []string{"0403"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { + "get years ago mediaitems with error in scanning", http.MethodGet, "/v1/explore/yearsAgo/:monthDate/mediaItems", "/v1/explore/yearsAgo/0403/mediaItems", []string{"monthDate"}, []string{"0403"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { mock.ExpectQuery(regexp.QuoteMeta(`FROM mediaitems`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + WithArgs(pgxmock.AnyArg(), "04", "03"). + WillReturnRows(pgxmock.NewRows(memoryMediaItemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime, "2023")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetYearsAgoMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get years ago mediaitems with 2 years", http.MethodGet, "/v1/explore/yearsAgo/:monthDate/mediaItems", "/v1/explore/yearsAgo/0403/mediaItems", []string{"monthDate"}, []string{"0403"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), "04", "03"). + WillReturnRows(getMockedMemoryMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetYearsAgoMediaItems + }, http.StatusOK, memoryMediaItemsResponseBody, }, } executeTests(t, tests) @@ -181,69 +124,45 @@ func TestGetYearsAgoMediaItems(t *testing.T) { func TestGetPlaces(t *testing.T) { tests := []Test{ { - "get places with empty table", - http.MethodGet, - "/v1/explore/places", - "/v1/explore/places", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(sqlmock.NewRows(placeCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get places with empty table", http.MethodGet, "/v1/explore/places", "/v1/explore/places", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaces - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get places with 2 rows", - http.MethodGet, - "/v1/explore/places", - "/v1/explore/places", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get places with error", http.MethodGet, "/v1/explore/places", "/v1/explore/places", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaces - }, - http.StatusOK, - placesResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get places with error", - http.MethodGet, - "/v1/explore/places", - "/v1/explore/places", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get places with error in scanning", http.MethodGet, "/v1/explore/places", "/v1/explore/places", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolTrue, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPlaces + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get places with 2 rows", http.MethodGet, "/v1/explore/places", "/v1/explore/places", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPlaceRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaces - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, placesResponseBody, }, } executeTests(t, tests) @@ -252,87 +171,50 @@ func TestGetPlaces(t *testing.T) { func TestGetPlace(t *testing.T) { tests := []Test{ { - "get place bad request", - http.MethodGet, - "/v1/explore/places/:id", - "/v1/explore/places/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place bad request", http.MethodGet, "/v1/explore/places/:id", "/v1/explore/places/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlace - }, - http.StatusBadRequest, - "invalid place id", + }, http.StatusBadRequest, "invalid place id", }, { - "get place not found", - http.MethodGet, - "/v1/explore/places/:id", - "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(sqlmock.NewRows(placeCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place not found", http.MethodGet, "/v1/explore/places/:id", "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlace - }, - http.StatusNotFound, - "place not found", + }, http.StatusNotFound, "place not found", }, { - "get place", - http.MethodGet, - "/v1/explore/places/:id", - "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place with error", http.MethodGet, "/v1/explore/places/:id", "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlace - }, - http.StatusOK, - placeResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get place with error", - http.MethodGet, - "/v1/explore/places/:id", - "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place with error in scanning", http.MethodGet, "/v1/explore/places/:id", "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolTrue, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlace - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get place with success", http.MethodGet, "/v1/explore/places/:id", "/v1/explore/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPlaceRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPlace + }, http.StatusOK, placeResponseBody, }, } executeTests(t, tests) @@ -341,468 +223,97 @@ func TestGetPlace(t *testing.T) { func TestGetPlaceMediaItems(t *testing.T) { tests := []Test{ { - "get place mediaitems bad request", - http.MethodGet, - "/v1/places/:id/mediaItems", - "/v1/places/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetPlaceMediaItems - }, - http.StatusBadRequest, - "invalid place id", - }, - { - "get place mediaitems not found", - http.MethodGet, - "/v1/places/:id/mediaItems", - "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place mediaitems bad request", http.MethodGet, "/v1/places/:id/mediaItems", "/v1/places/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaceMediaItems - }, - http.StatusOK, - "[]", + }, http.StatusBadRequest, "invalid place id", }, { - "get place mediaitems with 2 rows", - http.MethodGet, - "/v1/places/:id/mediaItems", - "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get place mediaitems not found", http.MethodGet, "/v1/places/:id/mediaItems", "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaceMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusOK, "[]", }, { - "get place mediaitems with error", - http.MethodGet, - "/v1/places/:id/mediaItems", - "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). + "get place mediaitems with error", http.MethodGet, "/v1/places/:id/mediaItems", "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPlaceMediaItems - }, - http.StatusInternalServerError, - "some db error", - }, - } - executeTests(t, tests) -} - -func TestGetThings(t *testing.T) { - tests := []Test{ - { - "get things with empty table", - http.MethodGet, - "/v1/explore/things", - "/v1/explore/things", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(sqlmock.NewRows(thingCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThings - }, - http.StatusOK, - "[]", - }, - { - "get things with 2 rows", - http.MethodGet, - "/v1/explore/things", - "/v1/explore/things", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThings - }, - http.StatusOK, - thingsResponseBody, - }, - { - "get things with error", - http.MethodGet, - "/v1/explore/things", - "/v1/explore/things", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThings - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, - } - executeTests(t, tests) -} - -func TestGetThing(t *testing.T) { - tests := []Test{ - { - "get thing bad request", - http.MethodGet, - "/v1/explore/things/:id", - "/v1/explore/things/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThing - }, - http.StatusBadRequest, - "invalid thing id", - }, - { - "get thing not found", - http.MethodGet, - "/v1/explore/things/:id", - "/v1/explore/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(sqlmock.NewRows(thingCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThing - }, - http.StatusNotFound, - "thing not found", - }, - { - "get thing", - http.MethodGet, - "/v1/explore/things/:id", - "/v1/explore/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThing - }, - http.StatusOK, - thingResponseBody, - }, - { - "get thing with error", - http.MethodGet, - "/v1/explore/things/:id", - "/v1/explore/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThing - }, - http.StatusInternalServerError, - "some db error", - }, - } - executeTests(t, tests) -} - -func TestGetThingMediaItems(t *testing.T) { - tests := []Test{ { - "get thing mediaitems bad request", - http.MethodGet, - "/v1/things/:id/mediaItems", - "/v1/things/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThingMediaItems - }, - http.StatusBadRequest, - "invalid thing id", - }, - { - "get thing mediaitems not found", - http.MethodGet, - "/v1/things/:id/mediaItems", - "/v1/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThingMediaItems - }, - http.StatusOK, - "[]", + "get place mediaitems with error in scanning", http.MethodGet, "/v1/places/:id/mediaItems", "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPlaceMediaItems + }, http.StatusInternalServerError, "Scanning value error", }, { - "get thing mediaitems with 2 rows", - http.MethodGet, - "/v1/things/:id/mediaItems", - "/v1/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). + "get place mediaitems with 2 rows", http.MethodGet, "/v1/places/:id/mediaItems", "/v1/places/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThingMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, - }, - { - "get thing mediaitems with error", - http.MethodGet, - "/v1/things/:id/mediaItems", - "/v1/things/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetThingMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPlaceMediaItems + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) } -func TestUpdatePeople(t *testing.T) { +func TestUpdatePerson(t *testing.T) { tests := []Test{ { - "update people bad request", - http.MethodPut, - "/v1/people/:id", - "/v1/people/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update people bad request", http.MethodPut, "/v1/people/:id", "/v1/people/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusBadRequest, - "invalid people id", + }, http.StatusBadRequest, "invalid people id", }, { - "update people with no payload", - http.MethodPut, - "/v1/people/:id", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update people with no payload", http.MethodPut, "/v1/people/:id", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusBadRequest, - "invalid people", + }, http.StatusBadRequest, "invalid people", }, { - "update people with bad payload", - http.MethodPut, - "/v1/people/:id", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update people with bad payload", http.MethodPut, "/v1/people/:id", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusBadRequest, - "invalid people", + }, http.StatusBadRequest, "invalid people", }, { - "update people with bad cover mediaitem id", - http.MethodPut, - "/v1/people/:id", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update people with bad cover mediaitem id", http.MethodPut, "/v1/people/:id", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","coverMediaItemId":"bad-mediaitem-id"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","coverMediaItemId":"bad-mediaitem-id"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusBadRequest, - "invalid people cover mediaitem id", + }, http.StatusBadRequest, "invalid people cover mediaitem id", }, { - "update people with success", - http.MethodPut, - "/v1/people/:id", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update people with error", http.MethodPut, "/v1/people/:id", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", true, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), "name", &sampleBoolTrue, &sampleCoverMediaItemID, pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "update people with error", - http.MethodPut, - "/v1/people/:id", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update people with success", http.MethodPut, "/v1/people/:id", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", true, - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","hidden":true,"coverMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), "name", &sampleBoolTrue, &sampleCoverMediaItemID, pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdatePerson - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -811,69 +322,40 @@ func TestUpdatePeople(t *testing.T) { func TestGetPeople(t *testing.T) { tests := []Test{ { - "get people with empty table", - http.MethodGet, - "/v1/explore/people", - "/v1/explore/people", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get people with empty table", http.MethodGet, "/v1/explore/people", "/v1/explore/people", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPeople - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get people with 2 rows", - http.MethodGet, - "/v1/explore/people", - "/v1/explore/people", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(getMockedPeopleRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitem_faces"`)). - WillReturnRows(getMockedMediaItemFaceRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get people with error", http.MethodGet, "/v1/explore/people", "/v1/explore/people", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPeople - }, - http.StatusOK, - peopleResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get people with error", - http.MethodGet, - "/v1/explore/people", - "/v1/explore/people", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get people with error in scanning", http.MethodGet, "/v1/explore/people", "/v1/explore/people", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...)).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolTrue, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPeople - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get people with 2 rows", http.MethodGet, "/v1/explore/people", "/v1/explore/people", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPeopleRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPeople + }, http.StatusOK, peopleResponseBody, }, } executeTests(t, tests) @@ -882,234 +364,121 @@ func TestGetPeople(t *testing.T) { func TestGetPerson(t *testing.T) { tests := []Test{ { - "get person bad request", - http.MethodGet, - "/v1/explore/people/:id", - "/v1/explore/people/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get person bad request", http.MethodGet, "/v1/explore/people/:id", "/v1/explore/people/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPerson - }, - http.StatusBadRequest, - "invalid person id", + }, http.StatusBadRequest, "invalid person id", }, { - "get person not found", - http.MethodGet, - "/v1/explore/people/:id", - "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get person not found", http.MethodGet, "/v1/explore/people/:id", "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPerson - }, - http.StatusNotFound, - "person not found", + }, http.StatusNotFound, "person not found", }, { - "get people", - http.MethodGet, - "/v1/explore/people/:id", - "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(getMockedPeopleRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitem_faces"`)). - WillReturnRows(getMockedMediaItemFaceRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get person with error", http.MethodGet, "/v1/explore/people/:id", "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPerson - }, - http.StatusOK, - personResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get person with error", - http.MethodGet, - "/v1/explore/people/:id", - "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get person with error in scanning", http.MethodGet, "/v1/explore/people/:id", "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...)).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolTrue, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPerson + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get people with success", http.MethodGet, "/v1/explore/people/:id", "/v1/explore/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPeopleRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetPerson - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, personResponseBody, }, } executeTests(t, tests) } -func TestGetPeopleMediaItems(t *testing.T) { +func TestGetPersonMediaItems(t *testing.T) { tests := []Test{ { - "get people mediaitems bad request", - http.MethodGet, - "/v1/people/:id/mediaItems", - "/v1/people/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetPeopleMediaItems - }, - http.StatusBadRequest, - "invalid people id", + "get people mediaitems bad request", http.MethodGet, "/v1/people/:id/mediaItems", "/v1/people/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPersonMediaItems + }, http.StatusBadRequest, "invalid people id", }, { - "get people mediaitems not found", - http.MethodGet, - "/v1/people/:id/mediaItems", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetPeopleMediaItems - }, - http.StatusOK, - "[]", + "get people mediaitems not found", http.MethodGet, "/v1/people/:id/mediaItems", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPersonMediaItems + }, http.StatusOK, "[]", }, { - "get people mediaitems with 2 rows", - http.MethodGet, - "/v1/people/:id/mediaItems", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetPeopleMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + "get people mediaitems with error", http.MethodGet, "/v1/people/:id/mediaItems", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPersonMediaItems + }, http.StatusInternalServerError, "some db error", }, { - "get people mediaitems with error", - http.MethodGet, - "/v1/people/:id/mediaItems", - "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetPeopleMediaItems - }, - http.StatusInternalServerError, - "some db error", + "get people mediaitems with error in scanning", http.MethodGet, "/v1/people/:id/mediaItems", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPersonMediaItems + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get people mediaitems with 2 rows", http.MethodGet, "/v1/people/:id/mediaItems", "/v1/people/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetPersonMediaItems + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) } -func getMockedPlaceRow() *sqlmock.Rows { - return sqlmock.NewRows(placeCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "postcode", "town", "city", - "state", "country", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime) -} - -func getMockedPlaceRows() *sqlmock.Rows { - return sqlmock.NewRows(placeCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "postcode", "town", "city", - "state", "country", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "postcode", "town", "city", - "state", "country", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "false", sampleTime, sampleTime) -} - -func getMockedThingRow() *sqlmock.Rows { - return sqlmock.NewRows(thingCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime) +func getMockedPlaceRow() *pgxmock.Rows { + return pgxmock.NewRows(append(placeCols, coverMediaItemCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolTrue, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight) } -func getMockedThingRows() *sqlmock.Rows { - return sqlmock.NewRows(thingCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "false", sampleTime, sampleTime) +func getMockedPlaceRows() *pgxmock.Rows { + return pgxmock.NewRows(append(placeCols, coverMediaItemCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolTrue, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolFalse, &sampleCoverMediaItemID, sampleTime, sampleTime, &sampleCoverMediaItemID, &sampleCoverMediaItemID, &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight) } -func getMockedPeopleRow() *sqlmock.Rows { - return sqlmock.NewRows(peopleCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime) +func getMockedPeopleRow() *pgxmock.Rows { + return pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolTrue, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail") } -func getMockedPeopleRows() *sqlmock.Rows { - return sqlmock.NewRows(peopleCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "name", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "false", sampleTime, sampleTime) +func getMockedPeopleRows() *pgxmock.Rows { + return pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...)). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolTrue, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail"). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolFalse, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail") } -func getMockedMemoryMediaItemRows() *sqlmock.Rows { - return sqlmock.NewRows(memoryMediaItemCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "placeholder", "true", "false", "false", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", nil, sampleTime, sampleTime, "2023"). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "placeholder", "false", "true", "true", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", nil, sampleTime, sampleTime, "2022") +func getMockedMemoryMediaItemRows() *pgxmock.Rows { + return pgxmock.NewRows(memoryMediaItemCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime, "2023"). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolFalse, &sampleBoolTrue, &sampleBoolTrue, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime, "2022") } diff --git a/api/internal/handlers/handlers.go b/api/internal/handlers/handlers.go index 49a10a82..7e34bdb7 100644 --- a/api/internal/handlers/handlers.go +++ b/api/internal/handlers/handlers.go @@ -2,26 +2,29 @@ package handlers import ( "api/config" - "api/internal/models" "api/pkg/cache" + "api/pkg/database" + "api/pkg/services/api" "api/pkg/services/worker" + "errors" "fmt" "strconv" "strings" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "gorm.io/gorm" ) // Handler ... type Handler struct { Config *config.Config - DB *gorm.DB + DB database.DBInterface Worker worker.WorkerClient Cache cache.Provider } +var errInvalidMonthDate = errors.New("invalid monthDate") + const ( base = 10 bitSize = 32 @@ -42,11 +45,13 @@ func getOffsetAndLimit(ctx echo.Context) (int, int) { if err != nil { limit = defaultLimit } + return int((page - 1) * limit), int(limit) //nolint: gosec } func getRequestingUserID(ctx echo.Context) uuid.UUID { userID, _ := ctx.Get("userID").(string) + return uuid.FromStringOrNil(userID) } @@ -56,27 +61,33 @@ func getMonthAndDate(ctx echo.Context) (string, string, error) { if len(monthDate) == 4 { // MMDD return monthDate[:2], monthDate[2:], nil } - return "", "", fmt.Errorf("invalid monthDate: %s", monthDate) + + return "", "", fmt.Errorf("%w: %s", errInvalidMonthDate, monthDate) } func getMediaItemFilters(ctx echo.Context) string { filterQuery := "" mediaItemType := ctx.QueryParam("type") if mediaItemType != "" { - filterQuery += fmt.Sprintf(" AND mediaitem_type = '%s'", mediaItemType) + if _, ok := api.MediaItemType_value[mediaItemType]; ok { + filterQuery += fmt.Sprintf(" AND mediaitem_type = '%s'", mediaItemType) + } } mediaItemCategory := ctx.QueryParam("category") if mediaItemCategory != "" { - filterQuery += fmt.Sprintf(" AND mediaitem_category = '%s'", mediaItemCategory) + if _, ok := api.MediaItemCategory_value[mediaItemCategory]; ok { + filterQuery += fmt.Sprintf(" AND mediaitem_category = '%s'", mediaItemCategory) + } } mediaItemStatus := ctx.QueryParam("status") - if mediaItemStatus != "" && (mediaItemStatus == string(models.Unspecified) || - mediaItemStatus == string(models.Ready) || mediaItemStatus == string(models.Processing) || - mediaItemStatus == string(models.Failed)) { - filterQuery += fmt.Sprintf(" AND status = '%s'", mediaItemStatus) + if mediaItemStatus != "" { + if _, ok := api.MediaItemStatus_value[mediaItemStatus]; ok { + filterQuery += fmt.Sprintf(" AND status = '%s'", mediaItemStatus) + } } else { - filterQuery += fmt.Sprintf(" AND status = '%s'", string(models.Ready)) + filterQuery += fmt.Sprintf(" AND status = '%s'", api.MediaItemStatus_READY.String()) } + return filterQuery } @@ -84,13 +95,16 @@ func getAlbumSortOrder(ctx echo.Context) string { if ctx.QueryParam("sort") == "name" { return "name asc" } + return "updated_at desc" } func getAlbumShared(ctx echo.Context) bool { queryParam := ctx.QueryParam("shared") - if queryParam == "" || strings.ToLower(queryParam) == "false" || strings.ToLower(queryParam) != "true" { + if queryParam == "" || strings.ToLower(queryParam) == "false" || + strings.ToLower(queryParam) != "true" { return false } + return true } diff --git a/api/internal/handlers/handlers_test.go b/api/internal/handlers/handlers_test.go index 2614b116..ba3e0294 100644 --- a/api/internal/handlers/handlers_test.go +++ b/api/internal/handlers/handlers_test.go @@ -1,6 +1,10 @@ package handlers import ( + "api/config" + "api/internal/models" + "api/pkg/cache" + "api/pkg/services/worker" "context" "database/sql/driver" "encoding/json" @@ -14,19 +18,12 @@ import ( "testing" "time" - "api/config" - "api/internal/models" - "api/pkg/cache" - "api/pkg/services/worker" - - "github.com/DATA-DOG/go-sqlmock" "github.com/bluele/gcache" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/grpc" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" ) type Test struct { @@ -38,7 +35,7 @@ type Test struct { ParamValues []string Header map[string]string Body io.Reader - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) mockCache []func(interface{}, interface{}) (interface{}, error) mockWorkerClient *mockWorkerGRPCClient Handler func(handler *Handler) func(ctx echo.Context) error @@ -69,26 +66,19 @@ func executeTests(t *testing.T, tests []Test) { ctx.Set("userID", "4d05b5f6-17c2-475e-87fe-3fc8b9567179") if _, ok := test.Header[echo.HeaderAuthorization]; ok { var features models.Features - _ = json.Unmarshal([]byte(`{"albums":true,"explore":true,"places":true,"things":true,"people":true}`), &features) + _ = json.Unmarshal([]byte(`{"albums":true,"explore":true,"places":true,"people":true}`), &features) ctx.Set("features", features) } // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) + } + mockCache := &cache.InMemoryCache{ + Connection: gcache.New(1024).LRU().Build(), } - mockCache := &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} if test.mockCache != nil { mockCache = &cache.InMemoryCache{Connection: gcache.New(1024). LRU(). @@ -104,14 +94,12 @@ func executeTests(t *testing.T, tests []Test) { // handler handler := &Handler{ Config: &config.Config{ - Storage: config.Storage{DiskRoot: os.TempDir()}, - Auth: config.Auth{RefreshTTL: 60}, - Feature: config.Feature{Albums: true, Explore: true, Places: true, Things: true, People: true}, - ML: config.ML{Places: true, Classification: true, OCR: true, Faces: true, Search: true}, - }, - DB: mockGDB, - Cache: mockCache, - Worker: test.mockWorkerClient, + Storage: config.Storage{DiskRoot: os.TempDir()}, Auth: config.Auth{RefreshTTL: 60}, Feature: config.Feature{ + Albums: true, Explore: true, Places: true, People: true, + }, ML: config.ML{ + Places: true, Classification: true, OCR: true, Faces: true, Search: true, + }, + }, DB: mockDB, Cache: mockCache, Worker: test.mockWorkerClient, } err = test.Handler(handler)(ctx) if test.ExpectedResCode >= http.StatusBadRequest { @@ -132,13 +120,6 @@ type ( } ) -func (mwc *mockWorkerGRPCClient) MediaItemProcess(ctx context.Context, request *worker.MediaItemProcessRequest, opts ...grpc.CallOption) (*worker.MediaItemProcessResponse, error) { - if mwc.wantErr { - return nil, errors.New("some grpc error") - } - return &worker.MediaItemProcessResponse{Ok: mwc.wantOk}, nil -} - func (mwc *mockWorkerGRPCClient) GenerateEmbedding(ctx context.Context, request *worker.GenerateEmbeddingRequest, opts ...grpc.CallOption) (*worker.GenerateEmbeddingResponse, error) { if mwc.wantErr { return nil, errors.New("some grpc error") diff --git a/api/internal/handlers/jobs.go b/api/internal/handlers/jobs.go index 012ec55c..60fb9b6a 100644 --- a/api/internal/handlers/jobs.go +++ b/api/internal/handlers/jobs.go @@ -2,24 +2,36 @@ package handlers import ( "api/internal/models" + "api/pkg/services/api" "errors" + "log/slog" "net/http" "reflect" + "strings" + "time" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) -type ( - // JobRequest ... +type ( // JobRequest ... JobRequest struct { - Components *string `json:"components"` - Status *string `json:"status"` + Components []string `json:"components"` + Status *string `json:"status"` } ) +const ( + queryGetJob = `SELECT * FROM jobs WHERE user_id=$1 AND id=$2` + queryGetJobs = `SELECT * FROM jobs WHERE user_id=$1 ORDER BY created_at DESC OFFSET $2 LIMIT $3` + queryCheckJobExists = `SELECT COUNT(*) FROM jobs WHERE user_id=$1 AND status IN ($2, $3, $4)` + queryCreateJob = `INSERT INTO jobs (id, user_id, status, components, created_at, updated_at)` + + ` VALUES ($1, $2, $3, $4, $5, $6)` + queryUpdateJob = `UPDATE jobs SET status=$3, updated_at=$4 WHERE user_id=$1 AND id=$2` + queryQueueMediaItems = `INSERT INTO queue (id, user_id, mediaitem_id, components, status) SELECT gen_random_uuid(), user_id, id, $1, $2 FROM mediaitems` +) + // GetJob ... func (h *Handler) GetJob(ctx echo.Context) error { userID := getRequestingUserID(ctx) @@ -28,16 +40,20 @@ func (h *Handler) GetJob(ctx echo.Context) error { return err } job := models.Job{} - result := h.DB.Model(&models.Job{}). - Where("id=? AND user_id=?", uid, userID). - First(&job) - if result.Error != nil { - slog.Error("error getting job", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + jobComponents := "" + err = h.DB.QueryRow(ctx.Request().Context(), queryGetJob, userID, uid).Scan(&job.ID, &job.UserID, + &job.Status, &jobComponents, &job.CreatedAt, &job.UpdatedAt) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "job not found") } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting job", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + + job.Components = strings.Split(jobComponents, ",") + return ctx.JSON(http.StatusOK, job) } @@ -53,22 +69,28 @@ func (h *Handler) UpdateJob(ctx echo.Context) error { return err } if job.Status == models.JobRunning { - existingJob := models.Job{} - result := h.DB.Model(&models.Job{}). - Where("user_id=? AND status IN (?, ?, ?)", userID, string(models.JobPaused), string(models.JobScheduled), string(models.JobRunning)). - First(&existingJob) - if (models.Job{} != existingJob) || (result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound)) { - slog.Error("job already exists", "error", result.Error) + existingJobCount := 0 + err = h.DB.QueryRow(ctx.Request().Context(), queryCheckJobExists, userID, string(models.JobPaused), + string(models.JobScheduled), string(models.JobRunning)). + Scan(&existingJobCount) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + slog.Error("error getting existing job count", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + if existingJobCount > 0 { + slog.Error("job already exists", "error", err) + return echo.NewHTTPError(http.StatusConflict, "job already exists") } } - result := h.DB.Model(&models.Job{UserID: userID, ID: uid}).Updates(map[string]interface{}{ - "Status": job.Status, - }) - if result.Error != nil { - slog.Error("error updating job", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryUpdateJob, userID, uid, job.Status, job.UpdatedAt) + if err != nil { + slog.Error("error updating job", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -77,16 +99,26 @@ func (h *Handler) GetJobs(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) jobs := []models.Job{} - result := h.DB.Model(&models.Job{}). - Where("user_id=?", userID). - Order("created_at desc"). - Find(&jobs). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting jobs", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetJobs, userID, offset, limit) + if err != nil { + slog.Error("error getting jobs", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + job := models.Job{} + jobComponents := "" + err = rows.Scan(&job.ID, &job.UserID, &job.Status, &jobComponents, &job.CreatedAt, &job.UpdatedAt) + if err != nil { + slog.Error("error scanning job", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + job.Components = strings.Split(jobComponents, ",") + jobs = append(jobs, job) } + return ctx.JSON(http.StatusOK, jobs) } @@ -100,18 +132,37 @@ func (h *Handler) CreateJob(ctx echo.Context) error { job.ID = uuid.NewV4() job.UserID = userID job.Status = models.JobScheduled - existingJob := models.Job{} - result := h.DB.Model(&models.Job{}). - Where("user_id=? AND status IN (?, ?, ?)", userID, string(models.JobPaused), string(models.JobScheduled), string(models.JobRunning)). - First(&existingJob) - if (models.Job{} != existingJob) || (result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound)) { - slog.Error("job already exists", "error", result.Error) + job.CreatedAt = time.Now() + job.UpdatedAt = job.CreatedAt + existingJobCount := 0 + err = h.DB.QueryRow(ctx.Request().Context(), queryCheckJobExists, userID, string(models.JobPaused), + string(models.JobScheduled), string(models.JobRunning)). + Scan(&existingJobCount) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + slog.Error("error getting existing job count", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + if existingJobCount > 0 { + slog.Error("job already exists", "error", err) + return echo.NewHTTPError(http.StatusConflict, "job already exists") } - if result := h.DB.Create(&job); result.Error != nil { - slog.Error("error creating job", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryCreateJob, job.ID, job.UserID, job.Status, + strings.Join(job.Components, ","), job.CreatedAt, job.UpdatedAt) + if err != nil { + slog.Error("error creating job", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + + _, err = h.DB.Exec(ctx.Request().Context(), queryQueueMediaItems, job.Components, api.MediaItemStatus_UNSPECIFIED) + if err != nil { + slog.Error("error queuing job mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return ctx.JSON(http.StatusCreated, job) } @@ -120,8 +171,10 @@ func getJobID(ctx echo.Context) (uuid.UUID, error) { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting job id", "error", err) + return uuid.Nil, echo.NewHTTPError(http.StatusBadRequest, "invalid job id") } + return uid, err } @@ -130,11 +183,19 @@ func getJob(ctx echo.Context) (*models.Job, error) { err := ctx.Bind(jobRequest) if err != nil { slog.Error("error getting job", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid job") } job := models.Job{} - if jobRequest.Components != nil { - job.Components = *jobRequest.Components + if len(jobRequest.Components) > 0 { + for _, jobComponent := range jobRequest.Components { + if _, ok := api.MediaItemComponent_value[jobComponent]; !ok { + slog.Error("error getting job component", "error", err) + + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid job component") + } + } + job.Components = jobRequest.Components } if jobRequest.Status != nil { job.Status = models.JobStatus(*jobRequest.Status) @@ -142,5 +203,6 @@ func getJob(ctx echo.Context) (*models.Job, error) { if reflect.DeepEqual(models.Job{}, job) { return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid job") } + return &job, nil } diff --git a/api/internal/handlers/jobs_test.go b/api/internal/handlers/jobs_test.go index 09d5a561..9fb5e601 100644 --- a/api/internal/handlers/jobs_test.go +++ b/api/internal/handlers/jobs_test.go @@ -1,113 +1,74 @@ package handlers import ( + "api/internal/models" "errors" "net/http" "regexp" "strings" "testing" - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) var ( jobCols = []string{ - "id", "user_id", "components", "status", "last_mediaitem_id", "created_at", "updated_at", + "id", "user_id", "status", "components", "created_at", "updated_at", } jobResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"status":"SCHEDULED","components":"metadata,places","lastMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + + `"status":"SCHEDULED","components":["METADATA","PLACES"],` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30"}` jobsResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"status":"RUNNING","components":"metadata,places","lastMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + + `"status":"RUNNING","components":["METADATA","PLACES"],` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30"},` + `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180","userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + - `"status":"RUNNING","components":"faces","lastMediaItemId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + + `"status":"RUNNING","components":["FACES"],` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30"}]` ) func TestGetJob(t *testing.T) { tests := []Test{ { - "get job bad request", - http.MethodGet, - "/v1/jobs/:id", - "/v1/jobs/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get job bad request", http.MethodGet, "/v1/jobs/:id", "/v1/jobs/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJob - }, - http.StatusBadRequest, - "invalid job id", + }, http.StatusBadRequest, "invalid job id", }, { - "get job not found", - http.MethodGet, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(sqlmock.NewRows(jobCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get job not found", http.MethodGet, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(jobCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJob - }, - http.StatusNotFound, - "job not found", + }, http.StatusNotFound, "job not found", }, { - "get job", - http.MethodGet, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(getMockedJobRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get job with error", http.MethodGet, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJob - }, - http.StatusOK, - jobResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get job with error", - http.MethodGet, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get job with error in scanning", http.MethodGet, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(jobCols).AddRow(pgxmock.AnyArg(), pgxmock.AnyArg(), "SCHEDULED", []string{"METADATA", "PLACES"}, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJob - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get job with success", http.MethodGet, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedJobRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetJob + }, http.StatusOK, jobResponseBody, }, } executeTests(t, tests) @@ -116,138 +77,71 @@ func TestGetJob(t *testing.T) { func TestUpdateJob(t *testing.T) { tests := []Test{ { - "update job bad request", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update job bad request", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusBadRequest, - "invalid job id", + }, http.StatusBadRequest, "invalid job id", }, { - "update job with no payload", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update job with no payload", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusBadRequest, - "invalid job", + }, http.StatusBadRequest, "invalid job", }, { - "update job with bad payload", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update job with bad payload", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusBadRequest, - "invalid job", + }, http.StatusBadRequest, "invalid job", }, { - "update job with success", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update job with error getting existing job count", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"status":"RUNNING"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(sqlmock.NewRows(jobCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "jobs"`)). - WithArgs("RUNNING", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"status":"RUNNING"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "update job with error", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update job with error due to existing job", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"status":"PAUSED"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "jobs"`)). - WithArgs("PAUSED", sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). + }, strings.NewReader(`{"status":"RUNNING"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.UpdateJob + }, http.StatusConflict, "job already exists", + }, + { + "update job with error", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"status":"RUNNING"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE jobs`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, models.JobRunning, pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "update job with error due to job already exists", - http.MethodPut, - "/v1/jobs/:id", - "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update job with success", http.MethodPut, "/v1/jobs/:id", "/v1/jobs/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"status":"RUNNING"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(getMockedJobRow()) - - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"status":"RUNNING"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE jobs`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, models.JobRunning, pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateJob - }, - http.StatusConflict, - "job already exists", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -256,67 +150,40 @@ func TestUpdateJob(t *testing.T) { func TestGetJobs(t *testing.T) { tests := []Test{ { - "get jobs with empty table", - http.MethodGet, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(sqlmock.NewRows(jobCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get jobs with empty table", http.MethodGet, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(jobCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJobs - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get jobs with 2 rows", - http.MethodGet, - "/v1/jobs", - "/v1/jobs?sort=updatedAt", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(getMockedJobRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get jobs with error", http.MethodGet, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJobs - }, - http.StatusOK, - jobsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get jobs with error", - http.MethodGet, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get jobs with error in scanning", http.MethodGet, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(jobCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "SCHEDULED", []string{"METADATA", "PLACES"}, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetJobs - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get jobs with 2 rows", http.MethodGet, "/v1/jobs", "/v1/jobs?sort=updatedAt", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedJobRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetJobs + }, http.StatusOK, jobsResponseBody, }, } executeTests(t, tests) @@ -325,136 +192,109 @@ func TestGetJobs(t *testing.T) { func TestCreateJob(t *testing.T) { tests := []Test{ { - "create job with bad payload", - http.MethodPost, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{ + "create job with bad payload", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateJob - }, - http.StatusBadRequest, - "invalid job", + }, http.StatusBadRequest, "invalid job", }, { - "create job with no payload", - http.MethodPost, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "create job with bad component", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"components":["INVALID"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateJob - }, - http.StatusBadRequest, - "invalid job", + }, http.StatusBadRequest, "invalid job component", }, { - "create job with success", - http.MethodPost, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{ + "create job with no payload", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.CreateJob + }, http.StatusBadRequest, "invalid job", + }, + { + "create job with error getting existing job count", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"components":"search"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(sqlmock.NewRows(jobCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "jobs"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "SCHEDULED", "search", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"components":["SEARCH"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateJob - }, - http.StatusCreated, - `"status":"SCHEDULED","components":"search",`, + }, http.StatusInternalServerError, "some db error", + }, + { + "create job with error due to existing job", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"components":["SEARCH"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.CreateJob + }, http.StatusConflict, "job already exists", }, { - "create job with error", - http.MethodPost, - "/v1/jobs", - "/v1/jobs", + "create job with error inserting job", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, }, - strings.NewReader(`{"components":"search"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(sqlmock.NewRows(jobCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "jobs"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), "SCHEDULED", "search", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). + strings.NewReader(`{"components":["SEARCH"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), models.JobScheduled, "SEARCH", pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateJob - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "create job with error due to job already exists", - http.MethodPost, - "/v1/jobs", - "/v1/jobs", - []string{}, - []string{}, - map[string]string{ + "create job with error queuing job mediaitems", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"components":"search"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "jobs"`)). - WillReturnRows(getMockedJobRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"components":["SEARCH"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), models.JobScheduled, "SEARCH", pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO queue`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateJob - }, - http.StatusConflict, - "job already exists", + }, http.StatusInternalServerError, "some db error", + }, + { + "create job with success", http.MethodPost, "/v1/jobs", "/v1/jobs", []string{}, []string{}, map[string]string{ + echo.HeaderContentType: echo.MIMEApplicationJSON, + }, strings.NewReader(`{"components":["SEARCH"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO jobs`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), models.JobScheduled, "SEARCH", pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO queue`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.CreateJob + }, http.StatusCreated, `"status":"SCHEDULED","components":["SEARCH"],`, }, } executeTests(t, tests) } -func getMockedJobRow() *sqlmock.Rows { - return sqlmock.NewRows(jobCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "metadata,places", "SCHEDULED", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sampleTime, sampleTime) +func getMockedJobRow() *pgxmock.Rows { + return pgxmock.NewRows(jobCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "SCHEDULED", "METADATA,PLACES", sampleTime, sampleTime) } -func getMockedJobRows() *sqlmock.Rows { - return sqlmock.NewRows(jobCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "metadata,places", "RUNNING", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "faces", "RUNNING", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", sampleTime, sampleTime) +func getMockedJobRows() *pgxmock.Rows { + return pgxmock.NewRows(jobCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "RUNNING", "METADATA,PLACES", sampleTime, sampleTime). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "RUNNING", "FACES", sampleTime, sampleTime) } diff --git a/api/internal/handlers/library.go b/api/internal/handlers/library.go index d90f534d..cdba9880 100644 --- a/api/internal/handlers/library.go +++ b/api/internal/handlers/library.go @@ -2,11 +2,23 @@ package handlers import ( "api/internal/models" + "log/slog" "net/http" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" +) + +const ( + queryGetFavouriteMediaItems = `SELECT * FROM mediaitems WHERE user_id=$1 AND is_favourite=true AND is_deleted=false OFFSET $2 LIMIT $3` + queryGetHiddenMediaItems = `SELECT * FROM mediaitems WHERE user_id=$1 AND is_hidden=true AND is_deleted=false OFFSET $2 LIMIT $3` + queryGetDeletedMediaItems = `SELECT * FROM mediaitems WHERE user_id=$1 AND is_deleted=true OFFSET $2 LIMIT $3` + queryAddFavouriteMediaItems = `UPDATE mediaitems SET is_favourite=true WHERE user_id=$1 AND id=ANY($2)` + queryRemoveFavouriteMediaItems = `UPDATE mediaitems SET is_favourite=false WHERE user_id=$1 AND id=ANY($2)` + queryAddHiddenMediaItems = `UPDATE mediaitems SET is_hidden=true WHERE user_id=$1 AND id=ANY($2)` + queryRemoveHiddenMediaItems = `UPDATE mediaitems SET is_hidden=false WHERE user_id=$1 AND id=ANY($2)` + queryAddDeletedMediaItems = `UPDATE mediaitems SET is_deleted=true WHERE user_id=$1 AND id=ANY($2)` + queryRemoveDeletedMediaItems = `UPDATE mediaitems SET is_deleted=false WHERE user_id=$1 AND id=ANY($2)` ) // GetFavouriteMediaItems ... @@ -14,12 +26,23 @@ func (h *Handler) GetFavouriteMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) favourites := []models.MediaItem{} - result := h.DB.Where("user_id=? AND is_favourite=true AND is_deleted=false", userID). - Find(&favourites).Offset(offset).Limit(limit) - if result.Error != nil { - slog.Error("error getting favourite mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetFavouriteMediaItems, userID, offset, limit) + if err != nil { + slog.Error("error getting favourite mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning favourite mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + favourites = append(favourites, mediaItem) + } + return ctx.JSON(http.StatusOK, favourites) } @@ -34,12 +57,13 @@ func (h *Handler) AddFavouriteMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_favourite": true}) - if result.Error != nil { - slog.Error("error adding favourite mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryAddFavouriteMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error adding favourite mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -54,12 +78,13 @@ func (h *Handler) RemoveFavouriteMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_favourite": false}) - if result.Error != nil { - slog.Error("error removing favourite mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryRemoveFavouriteMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error removing favourite mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -68,12 +93,23 @@ func (h *Handler) GetHiddenMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) hidden := []models.MediaItem{} - result := h.DB.Where("user_id=? AND is_hidden=true AND is_deleted=false", userID). - Find(&hidden).Offset(offset).Limit(limit) - if result.Error != nil { - slog.Error("error getting hidden mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetHiddenMediaItems, userID, offset, limit) + if err != nil { + slog.Error("error getting hidden mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning hidden mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + hidden = append(hidden, mediaItem) } + return ctx.JSON(http.StatusOK, hidden) } @@ -88,12 +124,13 @@ func (h *Handler) AddHiddenMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_hidden": true}) - if result.Error != nil { - slog.Error("error adding hidden mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryAddHiddenMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error adding hidden mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -108,12 +145,13 @@ func (h *Handler) RemoveHiddenMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_hidden": false}) - if result.Error != nil { - slog.Error("error removing hidden mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryRemoveHiddenMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error removing hidden mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -122,11 +160,23 @@ func (h *Handler) GetDeletedMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) offset, limit := getOffsetAndLimit(ctx) deleted := []models.MediaItem{} - result := h.DB.Where("user_id=? AND is_deleted=true", userID).Find(&deleted).Offset(offset).Limit(limit) - if result.Error != nil { - slog.Error("error getting deleted mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetDeletedMediaItems, userID, offset, limit) + if err != nil { + slog.Error("error getting deleted mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning deleted mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + deleted = append(deleted, mediaItem) + } + return ctx.JSON(http.StatusOK, deleted) } @@ -141,12 +191,13 @@ func (h *Handler) AddDeletedMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_deleted": true}) - if result.Error != nil { - slog.Error("error adding deleted mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryAddDeletedMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error adding deleted mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -161,11 +212,12 @@ func (h *Handler) RemoveDeletedMediaItems(ctx echo.Context) error { for idx, mediaItem := range mediaItems { mediaItemIDs[idx] = mediaItem.ID } - result := h.DB.Model(&models.MediaItem{}).Where("user_id=? AND id IN ?", userID, mediaItemIDs). - Updates(map[string]interface{}{"is_deleted": false}) - if result.Error != nil { - slog.Error("error removing deleted mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryRemoveDeletedMediaItems, userID, mediaItemIDs) + if err != nil { + slog.Error("error removing deleted mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } diff --git a/api/internal/handlers/library_test.go b/api/internal/handlers/library_test.go index f132d7b1..403c2b14 100644 --- a/api/internal/handlers/library_test.go +++ b/api/internal/handlers/library_test.go @@ -7,74 +7,48 @@ import ( "strings" "testing" - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) func TestGetFavouriteMediaItems(t *testing.T) { tests := []Test{ { - "get favourite mediaitems with empty table", - http.MethodGet, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get favourite mediaitems with empty table", http.MethodGet, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetFavouriteMediaItems - }, - http.StatusOK, - "[]", - }, - { - "get favourite mediaitems with 2 rows", - http.MethodGet, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetFavouriteMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, - }, - { - "get favourite mediaitems with error", - http.MethodGet, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). + }, http.StatusOK, "[]", + }, + { + "get favourite mediaitems with error", http.MethodGet, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetFavouriteMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "get favourite mediaitems with error in scanning", http.MethodGet, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetFavouriteMediaItems + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get favourite mediaitems with 2 rows", http.MethodGet, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetFavouriteMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -83,94 +57,40 @@ func TestGetFavouriteMediaItems(t *testing.T) { func TestAddFavouriteMediaItems(t *testing.T) { tests := []Test{ { - "add favourite mediaitems with bad payload", - http.MethodPost, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + "add favourite mediaitems with bad payload", http.MethodPost, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddFavouriteMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "add favourite mediaitems with bad mediaitem", - http.MethodPost, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "add favourite mediaitems with bad mediaitem", http.MethodPost, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddFavouriteMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "add favourite mediaitems with success", - http.MethodPost, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "add favourite mediaitems with error", http.MethodPost, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddFavouriteMediaItems - }, - http.StatusNoContent, - "", - }, - { - "add favourite mediaitems with error", - http.MethodPost, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "add favourite mediaitems with success", http.MethodPost, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddFavouriteMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -179,94 +99,40 @@ func TestAddFavouriteMediaItems(t *testing.T) { func TestRemoveFavouriteMediaItems(t *testing.T) { tests := []Test{ { - "remove favourite mediaitems with bad payload", - http.MethodDelete, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + "remove favourite mediaitems with bad payload", http.MethodDelete, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveFavouriteMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "remove favourite mediaitems with bad mediaitem", - http.MethodDelete, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "remove favourite mediaitems with bad mediaitem", http.MethodDelete, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveFavouriteMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "remove favourite mediaitems with success", - http.MethodDelete, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "remove favourite mediaitems with error", http.MethodDelete, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveFavouriteMediaItems - }, - http.StatusNoContent, - "", - }, - { - "remove favourite mediaitems with error", - http.MethodDelete, - "/v1/favourites", - "/v1/favourites", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "remove favourite mediaitems with success", http.MethodDelete, "/v1/favourites", "/v1/favourites", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveFavouriteMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -275,67 +141,41 @@ func TestRemoveFavouriteMediaItems(t *testing.T) { func TestGetHiddenMediaItems(t *testing.T) { tests := []Test{ { - "get hidden mediaitems with empty table", - http.MethodGet, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetHiddenMediaItems - }, - http.StatusOK, - "[]", - }, - { - "get hidden mediaitems with 2 rows", - http.MethodGet, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get hidden mediaitems with empty table", http.MethodGet, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetHiddenMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, - }, - { - "get hidden mediaitems with error", - http.MethodGet, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). + }, http.StatusOK, "[]", + }, + { + "get hidden mediaitems with error", http.MethodGet, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetHiddenMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", + }, + { + "get hidden mediaitems with error in scanning", http.MethodGet, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetHiddenMediaItems + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get hidden mediaitems with 2 rows", http.MethodGet, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetHiddenMediaItems + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -344,94 +184,40 @@ func TestGetHiddenMediaItems(t *testing.T) { func TestAddHiddenMediaItems(t *testing.T) { tests := []Test{ { - "add hidden mediaitems with bad payload", - http.MethodPost, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + "add hidden mediaitems with bad payload", http.MethodPost, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddHiddenMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "add hidden mediaitems with bad mediaitem", - http.MethodPost, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "add hidden mediaitems with bad mediaitem", http.MethodPost, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddHiddenMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "add hidden mediaitems with success", - http.MethodPost, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "add hidden mediaitems with error", http.MethodPost, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddHiddenMediaItems - }, - http.StatusNoContent, - "", - }, - { - "add hidden mediaitems with error", - http.MethodPost, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "add hidden mediaitems with success", http.MethodPost, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddHiddenMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -440,94 +226,40 @@ func TestAddHiddenMediaItems(t *testing.T) { func TestRemoveHiddenMediaItems(t *testing.T) { tests := []Test{ { - "remove hidden mediaitems with bad payload", - http.MethodDelete, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + "remove hidden mediaitems with bad payload", http.MethodDelete, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveHiddenMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "remove hidden mediaitems with bad mediaitem", - http.MethodDelete, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "remove hidden mediaitems with bad mediaitem", http.MethodDelete, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveHiddenMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "remove hidden mediaitems with success", - http.MethodDelete, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "remove hidden mediaitems with error", http.MethodDelete, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveHiddenMediaItems - }, - http.StatusNoContent, - "", - }, - { - "remove hidden mediaitems with error", - http.MethodDelete, - "/v1/hidden", - "/v1/hidden", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "remove hidden mediaitems with success", http.MethodDelete, "/v1/hidden", "/v1/hidden", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveHiddenMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -536,67 +268,41 @@ func TestRemoveHiddenMediaItems(t *testing.T) { func TestGetDeletedMediaItems(t *testing.T) { tests := []Test{ { - "get deleted mediaitems with empty table", - http.MethodGet, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetDeletedMediaItems - }, - http.StatusOK, - "[]", - }, - { - "get deleted mediaitems with 2 rows", - http.MethodGet, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get deleted mediaitems with empty table", http.MethodGet, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetDeletedMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, - }, - { - "get deleted mediaitems with error", - http.MethodGet, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). + }, http.StatusOK, "[]", + }, + { + "get deleted mediaitems with error", http.MethodGet, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetDeletedMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "get deleted mediaitems with error in scanning", http.MethodGet, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetDeletedMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get deleted mediaitems with 2 rows", http.MethodGet, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetDeletedMediaItems + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -605,94 +311,40 @@ func TestGetDeletedMediaItems(t *testing.T) { func TestAddDeletedMediaItems(t *testing.T) { tests := []Test{ { - "add deleted mediaitems with bad payload", - http.MethodPost, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + "add deleted mediaitems with bad payload", http.MethodPost, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddDeletedMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "add deleted mediaitems with bad mediaitem", - http.MethodPost, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "add deleted mediaitems with bad mediaitem", http.MethodPost, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddDeletedMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "add deleted mediaitems with success", - http.MethodPost, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "add deleted mediaitems with error", http.MethodPost, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddDeletedMediaItems - }, - http.StatusNoContent, - "", - }, - { - "add deleted mediaitems with error", - http.MethodPost, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "add deleted mediaitems with success", http.MethodPost, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.AddDeletedMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -701,94 +353,40 @@ func TestAddDeletedMediaItems(t *testing.T) { func TestRemoveDeletedMediaItems(t *testing.T) { tests := []Test{ { - "remove deleted mediaitems with bad payload", - http.MethodDelete, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + "remove deleted mediaitems with bad payload", http.MethodDelete, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveDeletedMediaItems - }, - http.StatusBadRequest, - "invalid mediaitems", - }, - { - "remove deleted mediaitems with bad mediaitem", - http.MethodDelete, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitems", + }, + { + "remove deleted mediaitems with bad mediaitem", http.MethodDelete, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["bad-mediaitem-id"]}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["bad-mediaitem-id"]}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveDeletedMediaItems - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "remove deleted mediaitems with success", - http.MethodDelete, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusBadRequest, "invalid mediaitem id", + }, + { + "remove deleted mediaitems with error", http.MethodDelete, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveDeletedMediaItems - }, - http.StatusNoContent, - "", - }, - { - "remove deleted mediaitems with error", - http.MethodDelete, - "/v1/trash", - "/v1/trash", - []string{}, - []string{}, - map[string]string{ + }, http.StatusInternalServerError, "some db error", + }, + { + "remove deleted mediaitems with success", http.MethodDelete, "/v1/trash", "/v1/trash", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"mediaItems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"mediaitems":["4d05b5f6-17c2-475e-87fe-3fc8b9567179"]}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.RemoveDeletedMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) diff --git a/api/internal/handlers/mediaitems.go b/api/internal/handlers/mediaitems.go index 73c659d5..c31a6c2e 100644 --- a/api/internal/handlers/mediaitems.go +++ b/api/internal/handlers/mediaitems.go @@ -2,26 +2,25 @@ package handlers import ( "api/internal/models" - "api/pkg/services/worker" + "api/pkg/services/api" "context" - "crypto/rand" "crypto/sha256" "encoding/hex" "errors" "fmt" "io" - "math/big" + "log/slog" "mime/multipart" "net/http" "os" "reflect" "strconv" "strings" + "time" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) const ( @@ -32,10 +31,40 @@ const ( fileFlag = os.O_WRONLY | os.O_APPEND | os.O_CREATE filePermission = 0o644 + + queryGetMediaItemPlaces = `SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems m ON p.cover_mediaitem_id=m.id` + + ` WHERE p.user_id=$1 AND p.is_hidden=false AND p.id IN (SELECT place_id FROM place_mediaitems` + + ` WHERE mediaitem_id=$2) ORDER BY p.created_at DESC` + queryGetMediaItemPeople = `SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces mf ON p.cover_mediaitem_face_id=mf.id` + + ` WHERE p.user_id=$1 AND p.is_hidden=false AND p.id IN (SELECT people_id FROM people_mediaitems` + + ` WHERE mediaitem_id=$2) ORDER BY p.created_at DESC` + queryGetMediaItemAlbums = `SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems m ON a.cover_mediaitem_id=m.id` + + ` WHERE a.user_id=$1 AND a.is_hidden=false AND a.is_hidden=false AND a.id IN (SELECT album_id FROM album_mediaitems` + + ` WHERE mediaitem_id=$2) ORDER BY a.created_at DESC` + queryGetMediaItem = `SELECT * FROM mediaitems WHERE user_id=$1 AND id=$2` + queryUpdateMediaItem = `UPDATE mediaitems SET description=$3, is_favourite=$4, is_hidden=$5,` + + ` updated_at=$6 WHERE user_id=$1 AND id=$2` + queryDeleteMediaItem = `UPDATE mediaitems SET is_deleted=true, updated_at=$3 WHERE user_id=$1 AND id=$2` + queryGetMediaItems = `SELECT * FROM mediaitems WHERE user_id=$1 AND is_hidden=false AND is_deleted=false` + + ` %s ORDER BY created_at DESC OFFSET $2 LIMIT $3` + queryGetPlaceNewCoverMediaItem = `SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems pm` + + ` JOIN places p ON p.id = pm.place_id WHERE p.user_id=$1 AND p.cover_mediaitem_id=$2 AND pm.mediaitem_id!=$2` + queryGetPeopleNewCoverMediaItem = `SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems pm` + + ` JOIN people p ON p.id = pm.people_id WHERE p.user_id=$1 AND p.cover_mediaitem_id=$2 AND pm.mediaitem_id!=$2` + queryGetAlbumNewCoverMediaItem = `SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems am` + + ` JOIN albums a ON a.id = am.album_id WHERE a.user_id=$1 AND a.cover_mediaitem_id=$2 AND am.mediaitem_id!=$2` + queryUpdatePlaceCoverMediaItem = `UPDATE places SET cover_mediaitem_id = $3 WHERE user_id = $1 AND id = $2` + queryUpdateAlbumCoverMediaItem = `UPDATE albums SET cover_mediaitem_id = $3 WHERE user_id = $1 AND id = $2` + queryUpdatePeopleCoverMediaItem = `UPDATE people SET cover_mediaitem_id = $3 WHERE user_id = $1 AND id = $2` + queryInsertMediaItem = `INSERT INTO mediaitems (id, user_id, filename, mediaitem_type, mediaitem_category,` + + ` status, source_url, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)` + queryQueueMediaItem = `INSERT INTO queue(id, user_id, mediaitem_id, components, status) VALUES (gen_random_uuid(), $1, $2, $3, $4)` + queryUpdateMediaItemHash = `UPDATE mediaitems SET hash=$3 WHERE user_id=$1 AND id=$2` ) -type ( - // MediaItemRequest ... +type ( // MediaItemRequest ... MediaItemRequest struct { Description *string `json:"description"` IsFavourite *bool `json:"favourite"` @@ -56,39 +85,28 @@ func (h *Handler) GetMediaItemPlaces(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } - mediaItem := new(models.MediaItem) - mediaItem.ID = uid - mediaItem.UserID = userID places := []models.Place{} - err = h.DB.Model(&mediaItem).Preload("CoverMediaItem").Association("Places").Find(&places) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetMediaItemPlaces, userID, uid) if err != nil { slog.Error("error getting mediaitem places", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return ctx.JSON(http.StatusOK, places) -} + defer rows.Close() + for rows.Next() { + place, err := models.ScanRowsToPlace(rows) + if err != nil { + slog.Error("error scanning mediaitem place", "error", err) -// GetMediaItemThings ... -func (h *Handler) GetMediaItemThings(ctx echo.Context) error { - userID := getRequestingUserID(ctx) - id := ctx.Param("id") - uid, err := uuid.FromString(id) - if err != nil { - slog.Error("error getting mediaitem id", "error", err) - return echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") - } - mediaItem := new(models.MediaItem) - mediaItem.ID = uid - mediaItem.UserID = userID - things := []models.Thing{} - err = h.DB.Model(&mediaItem).Preload("CoverMediaItem").Association("Things").Find(&things) - if err != nil { - slog.Error("error getting mediaitem things", "error", err) - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + places = append(places, place) } - return ctx.JSON(http.StatusOK, things) + + return ctx.JSON(http.StatusOK, places) } // GetMediaItemPeople ... @@ -98,17 +116,27 @@ func (h *Handler) GetMediaItemPeople(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } - mediaItem := new(models.MediaItem) - mediaItem.ID = uid - mediaItem.UserID = userID people := []models.People{} - err = h.DB.Model(&mediaItem).Preload("CoverMediaItemFace").Association("People").Find(&people) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetMediaItemPeople, userID, uid) if err != nil { slog.Error("error getting mediaitem people", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + person, err := models.ScanRowsToPerson(rows) + if err != nil { + slog.Error("error scanning mediaitem person", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + people = append(people, person) + } + return ctx.JSON(http.StatusOK, people) } @@ -119,17 +147,27 @@ func (h *Handler) GetMediaItemAlbums(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } - mediaItem := new(models.MediaItem) - mediaItem.ID = uid - mediaItem.UserID = userID albums := []models.Album{} - err = h.DB.Model(&mediaItem).Preload("CoverMediaItem").Association("Albums").Find(&albums) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetMediaItemAlbums, userID, uid) if err != nil { slog.Error("error getting mediaitem albums", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + album, err := models.ScanRowsToAlbum(rows) + if err != nil { + slog.Error("error scanning mediaitem album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + albums = append(albums, album) + } + return ctx.JSON(http.StatusOK, albums) } @@ -140,17 +178,28 @@ func (h *Handler) GetMediaItem(ctx echo.Context) error { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } mediaItem := models.MediaItem{} - result := h.DB.Where("id=? AND user_id=?", uid, userID).First(&mediaItem) - if result.Error != nil { - slog.Error("error getting mediaitem", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + err = h.DB.QueryRow(ctx.Request().Context(), queryGetMediaItem, userID, uid).Scan(&mediaItem.ID, + &mediaItem.UserID, &mediaItem.Filename, &mediaItem.Hash, &mediaItem.Description, &mediaItem.MimeType, + &mediaItem.SourceURL, &mediaItem.PreviewURL, &mediaItem.ThumbnailURL, &mediaItem.Placeholder, + &mediaItem.IsFavourite, &mediaItem.IsHidden, &mediaItem.IsDeleted, &mediaItem.Status, &mediaItem.MediaItemType, + &mediaItem.MediaItemCategory, &mediaItem.Width, &mediaItem.Height, &mediaItem.CreationTime, + &mediaItem.CameraMake, &mediaItem.CameraModel, &mediaItem.FocalLength, &mediaItem.ApertureFnumber, + &mediaItem.IsoEquivalent, &mediaItem.ExposureTime, &mediaItem.Megapixels, &mediaItem.Latitude, + &mediaItem.Longitude, &mediaItem.FPS, &mediaItem.EXIFData, &mediaItem.DetectedText, &mediaItem.Caption, + &mediaItem.CreatedAt, &mediaItem.UpdatedAt) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "mediaitem not found") } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusOK, mediaItem) } @@ -167,11 +216,15 @@ func (h *Handler) UpdateMediaItem(ctx echo.Context) error { } mediaItem.ID = uid mediaItem.UserID = userID - result := h.DB.Model(&mediaItem).Updates(mediaItem) - if result.Error != nil { - slog.Error("error updating mediaItem", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + mediaItem.UpdatedAt = time.Now() + _, err = h.DB.Exec(ctx.Request().Context(), queryUpdateMediaItem, mediaItem.UserID, + mediaItem.ID, mediaItem.Description, mediaItem.IsFavourite, mediaItem.IsHidden, mediaItem.UpdatedAt) + if err != nil { + slog.Error("error updating mediaItem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -182,102 +235,87 @@ func (h *Handler) DeleteMediaItem(ctx echo.Context) error { if err != nil { return err } - deleted := true - mediaItem := models.MediaItem{ID: uid, UserID: userID, IsDeleted: &deleted} - result := h.DB.Model(&mediaItem).Updates(mediaItem) - if result.Error != nil { - slog.Error("error updating mediaItem", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + mediaItem := models.MediaItem{ + ID: uid, UserID: userID, UpdatedAt: time.Now(), } - // album - err = h.updateCoverMediaItems(uid) + _, err = h.DB.Exec(ctx.Request().Context(), queryDeleteMediaItem, mediaItem.UserID, + mediaItem.ID, mediaItem.UpdatedAt) + if err != nil { + slog.Error("error deleting mediaItem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + err = h.updateCoverMediaItems(ctx.Request().Context(), userID, uid) if err != nil { slog.Error("error updating associated cover mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } -func (h *Handler) updateCoverMediaItems(mediaItemID uuid.UUID) error { //nolint: funlen,cyclop +func (h *Handler) updateCoverMediaItems(ctx context.Context, userID, mediaItemID uuid.UUID) error { var ( - albumsToUpdate []models.Album - placesToUpdate []models.Place - thingsToUpdate []models.Thing - peopleToUpdate []models.People - ) - result := h.DB.Model(&models.Album{}).Preload("MediaItems").Where("cover_mediaitem_id = ?", mediaItemID).Find(&albumsToUpdate) - if result.Error != nil { - return fmt.Errorf("error getting albums: %w", result.Error) - } - for _, album := range albumsToUpdate { - randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(album.MediaItems)))) - var newCoverMediaItemID *uuid.UUID - newCoverMediaItemID = &album.MediaItems[randomIndex.Int64()].ID - if len(album.MediaItems) == 1 { - newCoverMediaItemID = nil + entities = []string{"album", "place", "people"} + entityGetQueries = []string{ + queryGetAlbumNewCoverMediaItem, queryGetPlaceNewCoverMediaItem, + queryGetPeopleNewCoverMediaItem, } - result := h.DB.Model(&models.Album{UserID: album.UserID, ID: album.ID}).Omit("MediaItems").Updates(map[string]interface{}{ - "CoverMediaItemID": newCoverMediaItemID, - }) - if result.Error != nil { - return fmt.Errorf("error updating album cover mediaitem: %w", result.Error) + entityUpdateQueries = []string{ + queryUpdateAlbumCoverMediaItem, queryUpdatePlaceCoverMediaItem, + queryUpdatePeopleCoverMediaItem, } + err error + ) + entityItemsToUpdate := make([][][]uuid.UUID, len(entities)) + mtx, err := h.DB.Begin(ctx) + if err != nil { + return fmt.Errorf("error starting transaction for updating cover mediaitem: %w", err) } - result = h.DB.Model(&models.Place{}).Preload("MediaItems").Where("cover_mediaitem_id = ?", mediaItemID).Find(&placesToUpdate) - if result.Error != nil { - return fmt.Errorf("error getting places: %w", result.Error) - } - for _, place := range placesToUpdate { - randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(place.MediaItems)))) - var newCoverMediaItemID *uuid.UUID - newCoverMediaItemID = &place.MediaItems[randomIndex.Int64()].ID - if len(place.MediaItems) == 1 { - newCoverMediaItemID = nil + defer func() { + if err != nil { + _ = mtx.Rollback(ctx) } - result := h.DB.Model(&models.Place{UserID: place.UserID, ID: place.ID}).Omit("MediaItems").Updates(map[string]interface{}{ - "CoverMediaItemID": newCoverMediaItemID, - }) - if result.Error != nil { - return fmt.Errorf("error updating place cover mediaitem: %w", result.Error) + }() + for idx, entity := range entities { + entityItemsToUpdate[idx], err = h.getEntityToUpdate(ctx, mtx, entity, entityGetQueries[idx], userID, mediaItemID) + if err != nil { + return err } } - result = h.DB.Model(&models.Thing{}).Preload("MediaItems").Where("cover_mediaitem_id = ?", mediaItemID).Find(&thingsToUpdate) - if result.Error != nil { - return fmt.Errorf("error getting things: %w", result.Error) - } - for _, thing := range thingsToUpdate { - randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(thing.MediaItems)))) - var newCoverMediaItemID *uuid.UUID - newCoverMediaItemID = &thing.MediaItems[randomIndex.Int64()].ID - if len(thing.MediaItems) == 1 { - newCoverMediaItemID = nil - } - result := h.DB.Model(&models.Thing{UserID: thing.UserID, ID: thing.ID}).Omit("MediaItems").Updates(map[string]interface{}{ - "CoverMediaItemID": newCoverMediaItemID, - }) - if result.Error != nil { - return fmt.Errorf("error updating thing cover mediaitem: %w", result.Error) + for idx, entityItemToUpdate := range entityItemsToUpdate { + for _, itemToUpdate := range entityItemToUpdate { + _, err = mtx.Exec(ctx, entityUpdateQueries[idx], userID, itemToUpdate[0], itemToUpdate[1]) + if err != nil { + return fmt.Errorf("error updating %s cover mediaitem: %w", entities[idx], err) + } } } - result = h.DB.Model(&models.People{}).Preload("MediaItems").Where("cover_mediaitem_id = ?", mediaItemID).Find(&peopleToUpdate) - if result.Error != nil { - return fmt.Errorf("error getting people: %w", result.Error) + if err = mtx.Commit(ctx); err != nil { + return fmt.Errorf("error committing transaction for updating cover mediaitem: %w", err) } - for _, person := range peopleToUpdate { - randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(person.MediaItems)))) - var newCoverMediaItemID *uuid.UUID - newCoverMediaItemID = &person.MediaItems[randomIndex.Int64()].ID - if len(person.MediaItems) == 1 { - newCoverMediaItemID = nil - } - result := h.DB.Model(&models.People{UserID: person.UserID, ID: person.ID}).Omit("MediaItems").Updates(map[string]interface{}{ - "CoverMediaItemID": newCoverMediaItemID, - }) - if result.Error != nil { - return fmt.Errorf("error updating people cover mediaitem: %w", result.Error) + + return nil +} + +func (h *Handler) getEntityToUpdate(ctx context.Context, tx pgx.Tx, entity, query string, userID, mediaItemID uuid.UUID) ([][]uuid.UUID, error) { + rows, err := tx.Query(ctx, query, userID, mediaItemID) + if err != nil { + return nil, fmt.Errorf("error getting %s new cover mediaitems: %w", entity, err) + } + defer rows.Close() + var entitiesToUpdate [][]uuid.UUID + for rows.Next() { + entityToUpdate := make([]uuid.UUID, 2) //nolint: mnd + err = rows.Scan(&entityToUpdate[0], &entityToUpdate[1]) + if err != nil { + return nil, fmt.Errorf("error scanning %s new cover mediaitem: %w", entity, err) } + entitiesToUpdate = append(entitiesToUpdate, entityToUpdate) } - return nil + + return entitiesToUpdate, nil } // GetMediaItems ... @@ -286,21 +324,29 @@ func (h *Handler) GetMediaItems(ctx echo.Context) error { offset, limit := getOffsetAndLimit(ctx) filters := getMediaItemFilters(ctx) mediaItems := []models.MediaItem{} - result := h.DB.Where("user_id=? AND is_hidden=false AND is_deleted=false"+filters, userID). - Find(&mediaItems). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting mediaitems", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), fmt.Sprintf(queryGetMediaItems, filters), userID, offset, limit) + if err != nil { + slog.Error("error getting mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning mediaitems", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) + } + return ctx.JSON(http.StatusOK, mediaItems) } // UploadMediaItems ... func (h *Handler) UploadMediaItems(ctx echo.Context) error { userID := getRequestingUserID(ctx) - features, _ := ctx.Get("features").(models.Features) command := "start, finish" session := "" var err error @@ -315,31 +361,33 @@ func (h *Handler) UploadMediaItems(ctx echo.Context) error { file, err := ctx.FormFile("file") if err != nil { slog.Error("error uploading mediaitem", "error", err) + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } openedFile, err := file.Open() if err != nil { slog.Error("error reading uploaded mediaitem", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } defer openedFile.Close() - components := []worker.MediaItemComponent{} - if strings.Contains(command, "finish") { - components = h.getComponents(features) - } + features, _ := ctx.Get("features").(models.Features) if strings.Contains(command, "start") { mediaItem := createNewMediaItem(userID, file.Filename) - result := h.DB.Create(&mediaItem) - if result.Error != nil { - slog.Error("error inserting mediaitem", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + mediaItem.SourceURL = fmt.Sprintf("%s/%s", h.Config.DiskRoot, mediaItem.ID) + _, err = h.DB.Exec(ctx.Request().Context(), queryInsertMediaItem, mediaItem.ID, mediaItem.UserID, + mediaItem.Filename, mediaItem.MediaItemType, mediaItem.MediaItemCategory, + mediaItem.Status, mediaItem.SourceURL, mediaItem.CreatedAt, mediaItem.UpdatedAt) + if err != nil { + slog.Error("error inserting mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - err = h.saveToDiskAndSendToWorker(userID.String(), mediaItem.ID.String(), - openedFile, components) + err = h.saveToDisk(ctx.Request().Context(), userID.String(), mediaItem.ID.String(), features, openedFile, strings.Contains(command, "finish")) if err != nil { return err } @@ -349,8 +397,7 @@ func (h *Handler) UploadMediaItems(ctx echo.Context) error { }) } - err = h.saveToDiskAndSendToWorker(userID.String(), session, - openedFile, components) + err = h.saveToDisk(ctx.Request().Context(), userID.String(), session, features, openedFile, strings.Contains(command, "finish")) if err != nil { return err } @@ -358,38 +405,38 @@ func (h *Handler) UploadMediaItems(ctx echo.Context) error { return ctx.JSON(http.StatusNoContent, nil) } -func (h *Handler) saveToDiskAndSendToWorker(userID, mediaItemID string, openedFile multipart.File, components []worker.MediaItemComponent) error { - dstFile, err := os.OpenFile(fmt.Sprintf("%s/%s", h.Config.Storage.DiskRoot, mediaItemID), fileFlag, filePermission) +func (h *Handler) saveToDisk(ctx context.Context, userID, mediaItemID string, features models.Features, openedFile multipart.File, finish bool) error { + dstFile, err := os.OpenFile(fmt.Sprintf("%s/%s", h.Config.DiskRoot, mediaItemID), fileFlag, filePermission) if err != nil { slog.Error("error opening file", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } _, err = io.Copy(dstFile, openedFile) if err != nil { slog.Error("error copying file", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - if len(components) != 0 { - err = h.generateHashForDuplicates(userID, mediaItemID, dstFile.Name()) + if finish { + err = h.generateHashForDuplicates(ctx, userID, mediaItemID, dstFile.Name()) if err != nil { if strings.Contains(err.Error(), "violates unique constraint") { slog.Error("error due to duplicate mediaitem", "error", err) + return echo.NewHTTPError(http.StatusConflict, "mediaitem already exists") } slog.Error("error while generating hash for mediaitem", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - _, err = h.Worker.MediaItemProcess(context.Background(), &worker.MediaItemProcessRequest{ - UserId: userID, - Id: mediaItemID, - FilePath: h.Config.Storage.DiskRoot, - Components: components, - }) + err = h.queueMediaItemForProcessing(ctx, userID, mediaItemID, features) if err != nil { - slog.Error("error sending mediaitem for processing", "error", err) + slog.Error("error queuing mediaitem for processing", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } } @@ -397,10 +444,33 @@ func (h *Handler) saveToDiskAndSendToWorker(userID, mediaItemID string, openedFi return nil } -func (h *Handler) generateHashForDuplicates(userID, mediaItemID, filePath string) error { +func (h *Handler) queueMediaItemForProcessing(ctx context.Context, userID, mediaItemID string, features models.Features) error { + components := fmt.Sprintf("%s,%s", api.MediaItemComponent_METADATA.String(), api.MediaItemComponent_PREVIEW_THUMBNAIL.String()) + if h.Config.ML.Places && features.Places { + components += ("," + api.MediaItemComponent_PLACES.String()) + } + if h.Config.Faces && features.People { + components += ("," + api.MediaItemComponent_FACES.String()) + } + if h.Config.OCR && features.Explore { + components += ("," + api.MediaItemComponent_OCR.String()) + } + if h.Config.Search && features.Explore { + components += ("," + api.MediaItemComponent_SEARCH.String()) + } + _, err := h.DB.Exec(ctx, queryQueueMediaItem, userID, mediaItemID, components, api.MediaItemStatus_UNSPECIFIED) + if err != nil { + return err + } + + return nil +} + +func (h *Handler) generateHashForDuplicates(ctx context.Context, userID, mediaItemID, filePath string) error { openedFile, err := os.Open(filePath) if err != nil { slog.Error("error opening file for generating hash", "error", err) + return err } defer openedFile.Close() @@ -408,6 +478,7 @@ func (h *Handler) generateHashForDuplicates(userID, mediaItemID, filePath string fileHash := sha256.New() if _, err := io.Copy(fileHash, openedFile); err != nil { slog.Error("error copying file for generating hash", "error", err) + return err } @@ -417,47 +488,27 @@ func (h *Handler) generateHashForDuplicates(userID, mediaItemID, filePath string mediaItem.ID = uuid.FromStringOrNil(mediaItemID) mediaItem.UserID = uuid.FromStringOrNil(userID) mediaItem.Hash = &mediaItemHash - result := h.DB.Model(&mediaItem).Updates(mediaItem) - if result.Error != nil { - slog.Error("error updating mediaitem hash", "error", result.Error) - return result.Error + _, err = h.DB.Exec(ctx, queryUpdateMediaItemHash, mediaItem.UserID, mediaItem.ID, mediaItem.Hash) + if err != nil { + slog.Error("error updating mediaitem hash", "error", err) + + return err } return nil } -//nolint:cyclop -func (h *Handler) getComponents(features models.Features) []worker.MediaItemComponent { - components := []worker.MediaItemComponent{ - worker.MediaItemComponent_METADATA, - worker.MediaItemComponent_PREVIEW_THUMBNAIL, - } - if h.Config.ML.Places && features.Places { - components = append(components, worker.MediaItemComponent_PLACES) - } - if h.Config.ML.Classification && features.Things { - components = append(components, worker.MediaItemComponent_CLASSIFICATION) - } - if h.Config.ML.OCR && features.Explore { - components = append(components, worker.MediaItemComponent_OCR) - } - if h.Config.ML.Search && features.Explore { - components = append(components, worker.MediaItemComponent_SEARCH) - } - if h.Config.ML.Faces && features.People { - components = append(components, worker.MediaItemComponent_FACES) - } - return components -} - func createNewMediaItem(userID uuid.UUID, fileName string) *models.MediaItem { mediaItem := new(models.MediaItem) mediaItem.ID = uuid.NewV4() mediaItem.UserID = userID mediaItem.Filename = fileName - mediaItem.MediaItemType = models.Unknown - mediaItem.MediaItemCategory = models.Default - mediaItem.Status = models.Processing + mediaItem.MediaItemType = api.MediaItemType_UNKNOWN.String() + mediaItem.MediaItemCategory = api.MediaItemCategory_DEFAULT.String() + mediaItem.Status = api.MediaItemStatus_UNSPECIFIED.String() + mediaItem.CreatedAt = time.Now() + mediaItem.UpdatedAt = mediaItem.CreatedAt + return mediaItem } @@ -467,15 +518,18 @@ func validateChunk(ctx echo.Context) (string, string, error) { if len(command) == 0 { slog.Error("error getting command for resumable upload") + return "", "", echo.NewHTTPError(http.StatusBadRequest, "invalid command for resumable upload") } if command != "start" && offset == 0 { slog.Error("error getting chunk offset for resumable upload") + return "", "", echo.NewHTTPError(http.StatusBadRequest, "invalid chunk offset for resumable upload") } session := ctx.Request().Header.Get(HeaderUploadChunkSession) if command != "start" && len(session) == 0 { slog.Error("error getting chunk session for resumable upload") + return "", "", echo.NewHTTPError(http.StatusBadRequest, "invalid chunk session for resumable upload") } @@ -487,8 +541,10 @@ func getMediaItemID(ctx echo.Context) (uuid.UUID, error) { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return uuid.Nil, echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem id") } + return uid, err } @@ -497,16 +553,16 @@ func getMediaItem(ctx echo.Context) (*models.MediaItem, error) { err := ctx.Bind(mediaItemRequest) if err != nil { slog.Error("error getting mediaitem", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem") } mediaItem := models.MediaItem{ - Description: mediaItemRequest.Description, - IsFavourite: mediaItemRequest.IsFavourite, - IsHidden: mediaItemRequest.IsHidden, - IsDeleted: mediaItemRequest.IsDeleted, + Description: mediaItemRequest.Description, IsFavourite: mediaItemRequest.IsFavourite, + IsHidden: mediaItemRequest.IsHidden, IsDeleted: mediaItemRequest.IsDeleted, } if reflect.DeepEqual(models.MediaItem{}, mediaItem) { return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid mediaitem") } + return &mediaItem, nil } diff --git a/api/internal/handlers/mediaitems_test.go b/api/internal/handlers/mediaitems_test.go index 2456beab..9e1407e9 100644 --- a/api/internal/handlers/mediaitems_test.go +++ b/api/internal/handlers/mediaitems_test.go @@ -1,6 +1,7 @@ package handlers import ( + "api/pkg/services/api" "errors" "image" "image/color" @@ -13,220 +14,124 @@ import ( "testing" "time" - "github.com/DATA-DOG/go-sqlmock" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) var ( - sampleTime, _ = time.Parse("2006-01-02 15:04:05 -0700", "2022-09-22 11:22:33 +0530") + sampleTime, _ = time.Parse("2006-01-02 15:04:05 -0700", "2022-09-22 11:22:33 +0530") + sampleDescription = "description" + sampleBoolTrue = true + sampleBoolFalse = false + sampleCameraMake = "camera_make" + sampleCameraModel = "camera_model" + sampleFocalLength = "focal_length" + sampleApertureFnumber = "aperture_fnumber" + sampleIsoEquivalent = "iso_equivalent" + sampleExposureTime = "exposure_time" + sampleMegapixels = "18.4" + sampleLatitude = 17.580249 + sampleLongitude = -70.278493 + sampleFPS = "fps" + sampleSourceURL = "source_url" + samplePreviewURL = "preview_url" + sampleThumbnailURL = "thumbnail_url" + samplePlaceholder = "placeholder" + sampleMediaItemType = "mediaitem_type" + sampleMediaItemCategory = "mediaitem_category" + sampleWidth = 720 + sampleHeight = 480 + + coverMediaItemCols = []string{ + "id", "user_id", "source_url", "preview_url", "thumbnail_url", "placeholder", "mediaitem_type", + "mediaitem_category", "width", "height", + } mediaitemCols = []string{ - "id", "user_id", "filename", "description", "mime_type", "source_url", "preview_url", "thumbnail_url", + "id", "user_id", "filename", "hash", "description", "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", "is_favourite", "is_hidden", "is_deleted", "status", "mediaitem_type", "mediaitem_category", "width", "height", "creation_time", "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "latitude", "longitude", "fps", "exif_data", "created_at", "updated_at", + "iso_equivalent", "exposure_time", "megapixels", "latitude", "longitude", "fps", "exif_data", + "detected_text", "caption", "created_at", "updated_at", + } + mediaitemFaceCols = []string{ + "id", "mediaitem_id", "people_id", "embeddings", "thumbnail", } - mediaitemFaceCols = []string{"id", "mediaitem_id", "people_id", "thumbnail"} mediaitemResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"READY",` + + `"mediaItemType":"PHOTO","mediaItemCategory":"DEFAULT","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + + `"megapixels":"18.4","latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30"}` mediaitemsResponseBody = `[{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":true,"hidden":false,"deleted":false,"status":"READY",` + + `"mediaItemType":"PHOTO","mediaItemCategory":"DEFAULT","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + + `"megapixels":"18.4","latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30"},{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567180",` + `"userId":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","filename":"filename",` + `"description":"description","mimeType":"mime_type","sourceUrl":"source_url","previewUrl":"preview_url",` + - `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":false,"hidden":true,"deleted":true,"status":"status",` + - `"mediaItemType":"mediaitem_type","mediaItemCategory":"mediaitem_category","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + + `"thumbnailUrl":"thumbnail_url","placeholder":"placeholder","favourite":false,"hidden":true,"deleted":true,"status":"READY",` + + `"mediaItemType":"VIDEO","mediaItemCategory":"DEFAULT","width":720,"height":480,"creationTime":"2022-09-22T11:22:33+05:30",` + `"cameraMake":"camera_make","cameraModel":"camera_model","focalLength":"focal_length",` + `"apertureFNumber":"aperture_fnumber","isoEquivalent":"iso_equivalent","exposureTime":"exposure_time",` + - `"latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + + `"megapixels":"18.4","latitude":17.580249,"longitude":-70.278493,"fps":"fps","createdAt":"2022-09-22T11:22:33+05:30",` + `"updatedAt":"2022-09-22T11:22:33+05:30"}]` ) func TestGetMediaItemPlaces(t *testing.T) { tests := []Test{ { - "get mediaitem places bad request", - http.MethodGet, - "/v1/mediaItems/:id/places", - "/v1/mediaItems/bad-uuid/places", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetMediaItemPlaces - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "get mediaitem places with empty table", - http.MethodGet, - "/v1/mediaItems/:id/places", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(placeCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem places bad request", http.MethodGet, "/v1/mediaItems/:id/places", "/v1/mediaItems/bad-uuid/places", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPlaces - }, - http.StatusOK, - "[]", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "get mediaitem places with success", - http.MethodGet, - "/v1/mediaItems/:id/places", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). - WillReturnRows(getMockedPlaceRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem places with empty table", http.MethodGet, "/v1/mediaItems/:id/places", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPlaces - }, - http.StatusOK, - placesResponseBody, + }, http.StatusOK, "[]", }, { - "get mediaitem places with error", - http.MethodGet, - "/v1/mediaItems/:id/places", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "place_mediaitems"`)). + "get mediaitem places with error", http.MethodGet, "/v1/mediaItems/:id/places", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPlaces - }, - http.StatusInternalServerError, - "some db error", - }, - } - executeTests(t, tests) -} - -func TestGetMediaItemThings(t *testing.T) { - tests := []Test{ - { - "get mediaitem things bad request", - http.MethodGet, - "/v1/mediaItems/:id/things", - "/v1/mediaItems/bad-uuid/things", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetMediaItemThings - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusInternalServerError, "some db error", }, { - "get mediaitem things with empty table", - http.MethodGet, - "/v1/mediaItems/:id/things", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/things", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(thingCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetMediaItemThings - }, - http.StatusOK, - "[]", - }, - { - "get mediaitem things with success", - http.MethodGet, - "/v1/mediaItems/:id/things", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/things", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). - WillReturnRows(getMockedThingRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetMediaItemThings - }, - http.StatusOK, - thingsResponseBody, + "get mediaitem places with error in scanning", http.MethodGet, "/v1/mediaItems/:id/places", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(placeCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &samplePostCode, &sampleCountry, &sampleLocality, &sampleArea, &sampleBoolTrue, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItemPlaces + }, http.StatusInternalServerError, "Scanning value error", }, { - "get mediaitem things with error", - http.MethodGet, - "/v1/mediaItems/:id/things", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/things", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "thing_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.GetMediaItemThings - }, - http.StatusInternalServerError, - "some db error", + "get mediaitem places with success", http.MethodGet, "/v1/mediaItems/:id/places", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/places", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM places p LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPlaceRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItemPlaces + }, http.StatusOK, placesResponseBody, }, } executeTests(t, tests) @@ -235,87 +140,46 @@ func TestGetMediaItemThings(t *testing.T) { func TestGetMediaItemPeople(t *testing.T) { tests := []Test{ { - "get mediaitem people bad request", - http.MethodGet, - "/v1/mediaItems/:id/people", - "/v1/mediaItems/bad-uuid/people", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem people bad request", http.MethodGet, "/v1/mediaItems/:id/people", "/v1/mediaItems/bad-uuid/people", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPeople - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "get mediaitem people with empty table", - http.MethodGet, - "/v1/mediaItems/:id/people", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem people with empty table", http.MethodGet, "/v1/mediaItems/:id/people", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPeople - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get mediaitem people with success", - http.MethodGet, - "/v1/mediaItems/:id/people", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnRows(getMockedPeopleRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitem_faces"`)). - WillReturnRows(getMockedMediaItemFaceRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem people with error", http.MethodGet, "/v1/mediaItems/:id/people", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPeople - }, - http.StatusOK, - peopleResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get mediaitem people with error", - http.MethodGet, - "/v1/mediaItems/:id/people", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "people_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem people with error in scanning", http.MethodGet, "/v1/mediaItems/:id/people", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(peopleCols, mediaitemFaceCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleBoolTrue, &sampleCoverMediaItemID, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleCoverMediaItemID, nil, "thumbnail")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItemPeople + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get mediaitem people with success", http.MethodGet, "/v1/mediaItems/:id/people", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/people", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.*, mf.* FROM people p LEFT JOIN mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedPeopleRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemPeople - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, peopleResponseBody, }, } executeTests(t, tests) @@ -324,87 +188,50 @@ func TestGetMediaItemPeople(t *testing.T) { func TestGetMediaItemAlbums(t *testing.T) { tests := []Test{ { - "get mediaitem albums bad request", - http.MethodGet, - "/v1/mediaItems/:id/albums", - "/v1/mediaItems/bad-uuid/albums", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem albums bad request", http.MethodGet, "/v1/mediaItems/:id/albums", "/v1/mediaItems/bad-uuid/albums", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemAlbums - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "get mediaitem albums with empty table", - http.MethodGet, - "/v1/mediaItems/:id/albums", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(albumCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem albums with empty table", http.MethodGet, "/v1/mediaItems/:id/albums", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...))) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemAlbums - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get mediaitem albums with success", - http.MethodGet, - "/v1/mediaItems/:id/albums", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(getMockedAlbumRows()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem albums with error", http.MethodGet, "/v1/mediaItems/:id/albums", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemAlbums - }, - http.StatusOK, - albumsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get mediaitem albums with error", - http.MethodGet, - "/v1/mediaItems/:id/albums", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem albums with error in scanning", http.MethodGet, "/v1/mediaItems/:id/albums", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...)). + AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItemAlbums + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get mediaitem albums with success", http.MethodGet, "/v1/mediaItems/:id/albums", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179/albums", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedAlbumRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItemAlbums - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, albumsResponseBody, }, } executeTests(t, tests) @@ -413,85 +240,45 @@ func TestGetMediaItemAlbums(t *testing.T) { func TestGetMediaItem(t *testing.T) { tests := []Test{ { - "get mediaitem bad request", - http.MethodGet, - "/v1/mediaItems/:id", - "/v1/mediaItems/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem bad request", http.MethodGet, "/v1/mediaItems/:id", "/v1/mediaItems/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItem - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "get mediaitem not found", - http.MethodGet, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem not found", http.MethodGet, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItem - }, - http.StatusNotFound, - "mediaitem not found", + }, http.StatusNotFound, "mediaitem not found", }, { - "get mediaitem", - http.MethodGet, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem with error", http.MethodGet, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItem - }, - http.StatusOK, - mediaitemResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get mediaitem with error", - http.MethodGet, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitem with error in scanning", http.MethodGet, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItem - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get mediaitem with success", http.MethodGet, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItem + }, http.StatusOK, mediaitemResponseBody, }, } executeTests(t, tests) @@ -500,114 +287,43 @@ func TestGetMediaItem(t *testing.T) { func TestUpdateMediaItem(t *testing.T) { tests := []Test{ { - "update mediaitem bad request", - http.MethodPut, - "/v1/mediaItems/:id", - "/v1/mediaItems/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update mediaitem bad request", http.MethodPut, "/v1/mediaItems/:id", "/v1/mediaItems/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateMediaItem - }, - http.StatusBadRequest, - "invalid mediaitem id", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "update mediaitem with no payload", - http.MethodPut, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update mediaitem with no payload", http.MethodPut, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateMediaItem - }, - http.StatusBadRequest, - "invalid mediaitem", + }, http.StatusBadRequest, "invalid mediaitem", }, { - "update mediaitem with bad payload", - http.MethodPut, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update mediaitem with bad payload", http.MethodPut, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateMediaItem - }, - http.StatusBadRequest, - "invalid mediaitem", + }, http.StatusBadRequest, "invalid mediaitem", }, { - "update mediaitem with success", - http.MethodPut, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update mediaitem with error", http.MethodPut, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"description":"description","favourite":true,"hidden":true}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "description", true, true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"description":"description","favourite":true,"hidden":true}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, &sampleDescription, &sampleBoolTrue, &sampleBoolTrue, pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateMediaItem - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "update mediaitem with error", - http.MethodPut, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update mediaitem with success", http.MethodPut, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"description":"description","favourite":true,"hidden":true}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "description", true, true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"description":"description","favourite":true,"hidden":true}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(sampleCoverMediaItemID, sampleCoverMediaItemID, &sampleDescription, &sampleBoolTrue, &sampleBoolTrue, pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateMediaItem - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -616,480 +332,257 @@ func TestUpdateMediaItem(t *testing.T) { func TestDeleteMediaItem(t *testing.T) { tests := []Test{ { - "delete mediaitem bad request", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.DeleteMediaItem - }, - http.StatusBadRequest, - "invalid mediaitem id", - }, - { - "delete mediaitem with success", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"thing_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(getMockedPeopleRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete mediaitem bad request", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusNoContent, - "", + }, http.StatusBadRequest, "invalid mediaitem id", }, { - "delete mediaitem with error deleting", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). + "delete mediaitem with error deleting", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "delete mediaitem with error getting albums", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete mediaitem with error starting transaction", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}).WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "delete mediaitem with error updating albums", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). + "delete mediaitem with error getting album new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error getting places", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error scanning album new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "invalid")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error updating places", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "delete mediaitem with error getting place new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error getting things", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error scanning place new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "invalid")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.DeleteMediaItem + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "delete mediaitem with error getting people new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error updating things", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"thing_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error scanning people new cover mediaitems", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "invalid")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.DeleteMediaItem + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "delete mediaitem with error updating album cover mediaitem", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error getting people", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"thing_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error updating place cover mediaitem", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", - }, - { - "delete mediaitem with error updating people", - http.MethodDelete, - "/v1/mediaItems/:id", - "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", true, - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "albums"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "place_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "thing_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"thing_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(getMockedPeopleRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people_mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error updating people cover mediaitem", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.DeleteMediaItem + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with error committing transaction", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit().WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.DeleteMediaItem + }, http.StatusInternalServerError, "some db error", + }, + { + "delete mediaitem with success", http.MethodDelete, "/v1/mediaItems/:id", "/v1/mediaItems/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (am.album_id) am.album_id, am.mediaitem_id FROM album_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"album_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.place_id) pm.place_id, pm.mediaitem_id FROM place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"place_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT ON (pm.people_id) pm.people_id, pm.mediaitem_id FROM people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows([]string{"people_id", "mediaitem_id"}).AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179")) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit() + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteMediaItem - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -1098,88 +591,49 @@ func TestDeleteMediaItem(t *testing.T) { func TestGetMediaItems(t *testing.T) { tests := []Test{ { - "get mediaitems with empty table", - http.MethodGet, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitems with empty table", http.MethodGet, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItems - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get mediaitems with 2 rows", - http.MethodGet, - "/v1/mediaItems", - "/v1/mediaItems?type=photo&category=panorama", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitems with error", http.MethodGet, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetMediaItems + }, http.StatusInternalServerError, "some db error", + }, + { + "get mediaitems with error in scanning", http.MethodGet, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusInternalServerError, "Scanning value error", }, { - "get mediaitems with 2 rows and filters", - http.MethodGet, - "/v1/mediaItems", - "/v1/mediaItems?status=FAILED", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). + "get mediaitems with 2 rows", http.MethodGet, "/v1/mediaItems", "/v1/mediaItems?type=PHOTO&category=PANORAMA", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusOK, mediaitemsResponseBody, }, { - "get mediaitems with error", - http.MethodGet, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get mediaitems with 2 rows and filters", http.MethodGet, "/v1/mediaItems", "/v1/mediaItems?status=FAILED", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -1192,342 +646,148 @@ func TestUploadMediaItems(t *testing.T) { sampleFile4, contentType4 := getMockedMediaItemFile(t) sampleFile5, contentType5 := getMockedMediaItemFile(t) sampleFile6, contentType6 := getMockedMediaItemFile(t) - sampleFile7, contentType7 := getMockedMediaItemFile(t) tests := []Test{ { - "upload mediaitems with invalid command", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ + "upload mediaitems with invalid command", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ HeaderUploadType: "resumable", - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusBadRequest, - "invalid command for resumable upload", + }, http.StatusBadRequest, "invalid command for resumable upload", }, { - "upload mediaitems with invalid offset", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - HeaderUploadType: "resumable", - HeaderUploadCommand: "finish", - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "upload mediaitems with invalid offset", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + HeaderUploadType: "resumable", HeaderUploadCommand: "finish", + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusBadRequest, - "invalid chunk offset for resumable upload", + }, http.StatusBadRequest, "invalid chunk offset for resumable upload", }, { - "upload mediaitems with invalid session", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - HeaderUploadType: "resumable", - HeaderUploadCommand: "finish", - HeaderUploadChunkOffset: "1024", - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "upload mediaitems with invalid session", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + HeaderUploadType: "resumable", HeaderUploadCommand: "finish", HeaderUploadChunkOffset: "1024", + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusBadRequest, - "invalid chunk session for resumable upload", + }, http.StatusBadRequest, "invalid chunk session for resumable upload", }, { - "upload mediaitems with error uploading for resumable", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - HeaderUploadType: "resumable", - HeaderUploadCommand: "start", - HeaderUploadChunkOffset: "0", - }, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "upload mediaitems with error uploading for resumable", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + HeaderUploadType: "resumable", HeaderUploadCommand: "start", HeaderUploadChunkOffset: "0", + }, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusBadRequest, - "request Content-Type isn't multipart/form-data", + }, http.StatusBadRequest, "request Content-Type isn't multipart/form-data", }, { - "upload mediaitems with error uploading", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "upload mediaitems with error uploading", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusBadRequest, - "request Content-Type isn't multipart/form-data", + }, http.StatusBadRequest, "request Content-Type isn't multipart/form-data", }, { - "upload mediaitems with error inserting mediaitem", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ + "upload mediaitems with error inserting mediaitem", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ echo.HeaderContentType: contentType, - }, - sampleFile, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitems"`)). + }, sampleFile, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusInternalServerError, "some db error", }, { - "upload mediaitems with error saving hash due to duplicates", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - echo.HeaderContentType: contentType7, - }, - sampleFile7, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("violates unique constraint")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { - return handler.UploadMediaItems - }, - http.StatusConflict, - "mediaitem already exists", - }, - { - "upload mediaitems with error saving hash in mediaitem process", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ + "upload mediaitems with error saving hash due to duplicates", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ echo.HeaderContentType: contentType2, - }, - sampleFile2, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, sampleFile2, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("violates unique constraint")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusConflict, "mediaitem already exists", }, { - "upload mediaitems with error sending file to worker due to error in mediaitem process", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ + "upload mediaitems with error saving hash in mediaitem process", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ echo.HeaderContentType: contentType3, - }, - sampleFile3, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - &mockWorkerGRPCClient{wantErr: true}, - func(handler *Handler) func(ctx echo.Context) error { + }, sampleFile3, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusInternalServerError, - "some grpc error", - }, - { - "upload mediaitems successfully", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - echo.HeaderContentType: contentType4, - echo.HeaderAuthorization: "atoken", - }, - sampleFile4, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - &mockWorkerGRPCClient{wantOk: true}, - func(handler *Handler) func(ctx echo.Context) error { + }, http.StatusInternalServerError, "some db error", + }, + { + "upload mediaitems with error queuing for processing", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + echo.HeaderContentType: contentType4, echo.HeaderAuthorization: "atoken", + }, sampleFile4, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO queue`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusCreated, - `"id"`, - }, - { - "upload mediaitems with error for resumable", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - HeaderUploadType: "resumable", - HeaderUploadCommand: "finish", - HeaderUploadChunkOffset: "100", - HeaderUploadChunkSession: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - echo.HeaderContentType: contentType5, - }, - sampleFile5, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - &mockWorkerGRPCClient{wantErr: true}, - func(handler *Handler) func(ctx echo.Context) error { + }, http.StatusInternalServerError, "some db error", + }, + { + "upload mediaitems successfully", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + echo.HeaderContentType: contentType5, echo.HeaderAuthorization: "atoken", + }, sampleFile5, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO queue`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusInternalServerError, - "some grpc error", - }, - { - "upload mediaitems successfully for resumable", - http.MethodPost, - "/v1/mediaItems", - "/v1/mediaItems", - []string{}, - []string{}, - map[string]string{ - HeaderUploadType: "resumable", - HeaderUploadCommand: "finish", - HeaderUploadChunkOffset: "100", - HeaderUploadChunkSession: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - echo.HeaderContentType: contentType6, - }, - sampleFile6, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - &mockWorkerGRPCClient{wantOk: true}, - func(handler *Handler) func(ctx echo.Context) error { + }, http.StatusCreated, `"id"`, + }, + { + "upload mediaitems successfully for resumable", http.MethodPost, "/v1/mediaItems", "/v1/mediaItems", []string{}, []string{}, map[string]string{ + HeaderUploadType: "resumable", HeaderUploadCommand: "finish", HeaderUploadChunkOffset: "100", HeaderUploadChunkSession: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", echo.HeaderContentType: contentType6, + }, sampleFile6, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO queue`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UploadMediaItems - }, - http.StatusNoContent, - ``, + }, http.StatusNoContent, ``, }, } executeTests(t, tests) } -func getMockedMediaItemRow() *sqlmock.Rows { - return sqlmock.NewRows(mediaitemCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "placeholder", "true", "false", "false", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", nil, sampleTime, sampleTime) -} - -func getMockedMediaItemRows() *sqlmock.Rows { - return sqlmock.NewRows(mediaitemCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "placeholder", "true", "false", "false", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", nil, sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "placeholder", "false", "true", "true", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", nil, sampleTime, sampleTime) +func getMockedMediaItemRow() *pgxmock.Rows { + return pgxmock.NewRows(mediaitemCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, + api.MediaItemStatus_READY.String(), api.MediaItemType_PHOTO.String(), api.MediaItemCategory_DEFAULT.String(), 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime) } -func getMockedMediaItemFaceRow() *sqlmock.Rows { - return sqlmock.NewRows(mediaitemFaceCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "thumbnail") +func getMockedMediaItemRows() *pgxmock.Rows { + return pgxmock.NewRows(mediaitemCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, + api.MediaItemStatus_READY.String(), api.MediaItemType_PHOTO.String(), api.MediaItemCategory_DEFAULT.String(), 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolFalse, &sampleBoolTrue, &sampleBoolTrue, + api.MediaItemStatus_READY.String(), api.MediaItemType_VIDEO.String(), api.MediaItemCategory_DEFAULT.String(), 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime) } func getMockedMediaItemFile(t *testing.T) (*io.PipeReader, string) { diff --git a/api/internal/handlers/sharing.go b/api/internal/handlers/sharing.go index 865331af..970d2163 100644 --- a/api/internal/handlers/sharing.go +++ b/api/internal/handlers/sharing.go @@ -3,12 +3,20 @@ package handlers import ( "api/internal/models" "errors" + "log/slog" "net/http" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" +) + +const ( + queryGetSharedAlbumMediaItems = `SELECT * FROM mediaitems WHERE id IN (SELECT mediaitem_id FROM album_mediaitems` + + ` WHERE album_id=$1) AND is_hidden=false AND is_deleted=false ORDER BY created_at DESC OFFSET $2 LIMIT $3` + queryGetSharedAlbum = `SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums a LEFT JOIN mediaitems m` + + ` ON a.cover_mediaitem_id=m.id WHERE a.is_shared=true AND a.id=$1` ) // GetSharedAlbumMediaItems ... @@ -18,14 +26,24 @@ func (h *Handler) GetSharedAlbumMediaItems(ctx echo.Context) error { if err != nil { return err } - sharedAlbum := new(models.Album) - sharedAlbum.ID = uid mediaItems := []models.MediaItem{} - err = h.DB.Model(&sharedAlbum).Offset(offset).Limit(limit).Association("MediaItems").Find(&mediaItems, "is_deleted=?", false) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetSharedAlbumMediaItems, uid, offset, limit) if err != nil { slog.Error("error getting shared album mediaitems", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + defer rows.Close() + for rows.Next() { + mediaItem, err := models.ScanRowsToMediaItem(rows) + if err != nil { + slog.Error("error scanning shared album mediaitem", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + mediaItems = append(mediaItems, mediaItem) + } + return ctx.JSON(http.StatusOK, mediaItems) } @@ -35,18 +53,29 @@ func (h *Handler) GetSharedAlbum(ctx echo.Context) error { if err != nil { return err } + sharedAlbum := models.Album{} - result := h.DB.Model(&models.Album{}). - Where("is_shared=true AND id=?", uid). - Preload("CoverMediaItem"). - First(&sharedAlbum) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + coverMediaItem := &models.CoverMediaItem{} + + err = h.DB.QueryRow(ctx.Request().Context(), queryGetSharedAlbum, uid).Scan(&sharedAlbum.ID, + &sharedAlbum.UserID, &sharedAlbum.Name, &sharedAlbum.Description, &sharedAlbum.IsShared, + &sharedAlbum.IsHidden, &sharedAlbum.MediaItemsCount, &sharedAlbum.CoverMediaItemID, &sharedAlbum.CreatedAt, + &sharedAlbum.UpdatedAt, &coverMediaItem.ID, &coverMediaItem.UserID, &coverMediaItem.SourceURL, + &coverMediaItem.PreviewURL, &coverMediaItem.ThumbnailURL, &coverMediaItem.Placeholder, + &coverMediaItem.MediaItemType, &coverMediaItem.MediaItemCategory, &coverMediaItem.Width, &coverMediaItem.Height) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "shared link not found") } - slog.Error("error getting shared album", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting shared album", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + if sharedAlbum.CoverMediaItemID != nil { + sharedAlbum.CoverMediaItem = coverMediaItem } + return ctx.JSON(http.StatusOK, sharedAlbum) } @@ -55,7 +84,9 @@ func getSharedAlbumID(ctx echo.Context) (uuid.UUID, error) { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting shared album id", "error", err) + return uuid.Nil, echo.NewHTTPError(http.StatusBadRequest, "invalid shared link") } + return uid, err } diff --git a/api/internal/handlers/sharing_test.go b/api/internal/handlers/sharing_test.go index 94723ba7..9fe5bacf 100644 --- a/api/internal/handlers/sharing_test.go +++ b/api/internal/handlers/sharing_test.go @@ -6,92 +6,52 @@ import ( "regexp" "testing" - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) func TestGetSharedAlbumMediaItems(t *testing.T) { tests := []Test{ { - "get shared album mediaitems bad request", - http.MethodGet, - "/v1/sharing/:id/mediaItems", - "/v1/sharing/bad-uuid/mediaItems", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album mediaitems bad request", http.MethodGet, "/v1/sharing/:id/mediaItems", "/v1/sharing/bad-uuid/mediaItems", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbumMediaItems - }, - http.StatusBadRequest, - "invalid shared link", + }, http.StatusBadRequest, "invalid shared link", }, { - "get shared album mediaitems not found", - http.MethodGet, - "/v1/sharing/:id/mediaItems", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(sqlmock.NewRows(mediaitemCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album mediaitems not found", http.MethodGet, "/v1/sharing/:id/mediaItems", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbumMediaItems - }, - http.StatusOK, - "[]", + }, http.StatusOK, "[]", }, { - "get shared album mediaitems with 2 rows", - http.MethodGet, - "/v1/sharing/:id/mediaItems", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnRows(getMockedMediaItemRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album mediaitems with error", http.MethodGet, "/v1/sharing/:id/mediaItems", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbumMediaItems - }, - http.StatusOK, - mediaitemsResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get shared album mediaitems with error", - http.MethodGet, - "/v1/sharing/:id/mediaItems", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`JOIN "album_mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album mediaitems with error in scanning", http.MethodGet, "/v1/sharing/:id/mediaItems", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(mediaitemCols).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "filename", nil, &sampleDescription, "mime_type", "source_url", "preview_url", "thumbnail_url", "placeholder", &sampleBoolTrue, &sampleBoolFalse, &sampleBoolFalse, "status", "mediaitem_type", "mediaitem_category", 720, 480, sampleTime, &sampleCameraMake, &sampleCameraModel, &sampleFocalLength, &sampleApertureFnumber, &sampleIsoEquivalent, &sampleExposureTime, &sampleMegapixels, &sampleLatitude, &sampleLongitude, &sampleFPS, nil, nil, nil, sampleTime, sampleTime)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetSharedAlbumMediaItems + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get shared album mediaitems with 2 rows", http.MethodGet, "/v1/sharing/:id/mediaItems", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179/mediaItems", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbumMediaItems - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, mediaitemsResponseBody, }, } executeTests(t, tests) @@ -100,87 +60,49 @@ func TestGetSharedAlbumMediaItems(t *testing.T) { func TestGetSharedAlbum(t *testing.T) { tests := []Test{ { - "get shared album bad request", - http.MethodGet, - "/v1/sharing/:id", - "/v1/sharing/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album bad request", http.MethodGet, "/v1/sharing/:id", "/v1/sharing/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbum - }, - http.StatusBadRequest, - "invalid shared link", + }, http.StatusBadRequest, "invalid shared link", }, { - "get shared album not found", - http.MethodGet, - "/v1/sharing/:id", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(sqlmock.NewRows(albumCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album not found", http.MethodGet, "/v1/sharing/:id", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(albumCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbum - }, - http.StatusNotFound, - "shared link not found", + }, http.StatusNotFound, "shared link not found", }, { - "get shared album", - http.MethodGet, - "/v1/sharing/:id", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(getMockedAlbumRow()) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album with error", http.MethodGet, "/v1/sharing/:id", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbum - }, - http.StatusOK, - albumResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get shared album with error", - http.MethodGet, - "/v1/sharing/:id", - "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get shared album with error in scanning", http.MethodGet, "/v1/sharing/:id", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...)).AddRow("invalid", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", &sampleDescription, &sampleBoolTrue, &sampleBoolFalse, &sampleMediaItemsCount, &sampleCoverMediaItemID, sampleTime, sampleTime, "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleSourceURL, &samplePreviewURL, &sampleThumbnailURL, &samplePlaceholder, &sampleMediaItemType, &sampleMediaItemCategory, &sampleWidth, &sampleHeight)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetSharedAlbum + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get shared album with success", http.MethodGet, "/v1/sharing/:id", "/v1/sharing/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,` + + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg()). + WillReturnRows(getMockedAlbumRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetSharedAlbum - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, albumResponseBody, }, } executeTests(t, tests) diff --git a/api/internal/handlers/users.go b/api/internal/handlers/users.go index cb8ac387..ded03a3f 100644 --- a/api/internal/handlers/users.go +++ b/api/internal/handlers/users.go @@ -5,17 +5,17 @@ import ( "crypto/sha512" "encoding/hex" "errors" + "log/slog" "net/http" "reflect" + "time" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" - "gorm.io/gorm" ) -type ( - // UserRequest ... +type ( // UserRequest ... UserRequest struct { Name *string `json:"name"` Username *string `json:"username"` @@ -24,6 +24,16 @@ type ( } ) +const ( + queryGetUser = `SELECT * FROM users WHERE id=$1` + queryGetUsers = `SELECT * FROM users ORDER BY created_at DESC OFFSET $1 LIMIT $2` + queryCreateUser = `INSERT INTO users (id, name, username, password, features, created_at, updated_at)` + + ` VALUES ($1, $2, $3, $4, $5, $6, $7)` + queryUpdateUser = `UPDATE users SET name = $2, username = $3, password = $4, features = $5,` + + ` updated_at = $6 WHERE id=$1` + queryDeleteUser = `DELETE FROM users WHERE id=$1` +) + // GetUser ... func (h *Handler) GetUser(ctx echo.Context) error { uid, err := getUserID(ctx) @@ -31,14 +41,16 @@ func (h *Handler) GetUser(ctx echo.Context) error { return err } user := models.User{} - result := h.DB.Model(&models.User{}).Where("id=?", uid).First(&user) - if result.Error != nil { - slog.Error("error getting user", "error", result.Error) - if errors.Is(result.Error, gorm.ErrRecordNotFound) { + err = h.DB.QueryRow(ctx.Request().Context(), queryGetUser, uid).Scan(&user.ID, &user.Name, &user.Username, &user.Password, &user.Features, &user.CreatedAt, &user.UpdatedAt) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound, "user not found") } - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + slog.Error("error getting user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusOK, user) } @@ -53,11 +65,14 @@ func (h *Handler) UpdateUser(ctx echo.Context) error { return err } user.ID = uid - result := h.DB.Model(&user).Updates(user) - if result.Error != nil { - slog.Error("error updating user", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + user.UpdatedAt = time.Now() + _, err = h.DB.Exec(ctx.Request().Context(), queryUpdateUser, user.ID, user.Name, user.Username, user.Password, user.Features, user.UpdatedAt) + if err != nil { + slog.Error("error updating user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -67,11 +82,13 @@ func (h *Handler) DeleteUser(ctx echo.Context) error { if err != nil { return err } - user := models.User{ID: uid} - if result := h.DB.Delete(&user); result.Error != nil { - slog.Error("error deleting user", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + _, err = h.DB.Exec(ctx.Request().Context(), queryDeleteUser, uid) + if err != nil { + slog.Error("error deleting user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusNoContent, nil) } @@ -79,14 +96,23 @@ func (h *Handler) DeleteUser(ctx echo.Context) error { func (h *Handler) GetUsers(ctx echo.Context) error { offset, limit := getOffsetAndLimit(ctx) users := []models.User{} - result := h.DB.Model(&models.User{}). - Find(&users). - Offset(offset). - Limit(limit) - if result.Error != nil { - slog.Error("error getting users", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + rows, err := h.DB.Query(ctx.Request().Context(), queryGetUsers, offset, limit) + if err != nil { + slog.Error("error getting users", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + defer rows.Close() + for rows.Next() { + user := models.User{} + if err := rows.Scan(&user.ID, &user.Name, &user.Username, &user.Password, &user.Features, &user.CreatedAt, &user.UpdatedAt); err != nil { + slog.Error("error scanning user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + users = append(users, user) } + return ctx.JSON(http.StatusOK, users) } @@ -97,10 +123,16 @@ func (h *Handler) CreateUser(ctx echo.Context) error { return err } user.ID = uuid.NewV4() - if result := h.DB.Create(&user); result.Error != nil { - slog.Error("error creating user", "error", result.Error) - return echo.NewHTTPError(http.StatusInternalServerError, result.Error.Error()) + user.CreatedAt = time.Now() + user.UpdatedAt = user.CreatedAt + _, err = h.DB.Exec(ctx.Request().Context(), queryCreateUser, user.ID, user.Name, + user.Username, user.Password, user.Features, user.CreatedAt, user.UpdatedAt) + if err != nil { + slog.Error("error creating user", "error", err) + + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } + return ctx.JSON(http.StatusCreated, user) } @@ -109,8 +141,10 @@ func getUserID(ctx echo.Context) (uuid.UUID, error) { uid, err := uuid.FromString(id) if err != nil { slog.Error("error getting user id", "error", err) + return uuid.Nil, echo.NewHTTPError(http.StatusBadRequest, "invalid user id") } + return uid, err } @@ -118,6 +152,7 @@ func getUser(ctx echo.Context) (*models.User, error) { UserRequest := new(UserRequest) if err := ctx.Bind(UserRequest); err != nil { slog.Error("error getting user", "error", err) + return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid user") } user := models.User{} @@ -136,11 +171,13 @@ func getUser(ctx echo.Context) (*models.User, error) { if reflect.DeepEqual(models.User{}, user) { return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid user") } + return &user, nil } func getPasswordHash(password string) string { passwordHash := sha512.New() passwordHash.Write([]byte(password)) + return hex.EncodeToString(passwordHash.Sum(nil)) } diff --git a/api/internal/handlers/users_test.go b/api/internal/handlers/users_test.go index 49be3d7c..84665c12 100644 --- a/api/internal/handlers/users_test.go +++ b/api/internal/handlers/users_test.go @@ -7,12 +7,15 @@ import ( "strings" "testing" - "github.com/DATA-DOG/go-sqlmock" + "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" ) var ( - userCols = []string{"id", "name", "username", "password", "created_at", "updated_at"} + userCols = []string{ + "id", "name", "username", "password", "features", "created_at", "updated_at", + } userResponseBody = `{"id":"4d05b5f6-17c2-475e-87fe-3fc8b9567179","name":"name",` + `"username":"username",` + `"createdAt":"2022-09-22T11:22:33+05:30","updatedAt":"2022-09-22T11:22:33+05:30"}` @@ -27,85 +30,41 @@ var ( func TestGetUser(t *testing.T) { tests := []Test{ { - "get user bad request", - http.MethodGet, - "/v1/users/:id", - "/v1/users/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get user bad request", http.MethodGet, "/v1/users/:id", "/v1/users/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUser - }, - http.StatusBadRequest, - "invalid user id", + }, http.StatusBadRequest, "invalid user id", }, { - "get user not found", - http.MethodGet, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(sqlmock.NewRows(userCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get user not found", http.MethodGet, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg()).WillReturnError(pgx.ErrNoRows) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUser - }, - http.StatusNotFound, - "user not found", + }, http.StatusNotFound, "user not found", }, { - "get user", - http.MethodGet, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(getMockedUserRow()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get user with error", http.MethodGet, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg()).WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUser - }, - http.StatusOK, - userResponseBody, + }, http.StatusInternalServerError, "some db error", }, { - "get user with error", - http.MethodGet, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get user with error in scanning", http.MethodGet, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg()).WillReturnRows(pgxmock.NewRows(userCols).AddRow("invalid", "name", "username", "password", "", "invalid", "invalid")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetUser + }, http.StatusInternalServerError, "Scanning value error", + }, + { + "get user with success", http.MethodGet, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg()).WillReturnRows(getMockedUserRow()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUser - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, userResponseBody, }, } executeTests(t, tests) @@ -114,114 +73,43 @@ func TestGetUser(t *testing.T) { func TestUpdateUser(t *testing.T) { tests := []Test{ { - "update user bad request", - http.MethodPut, - "/v1/users/:id", - "/v1/users/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update user bad request", http.MethodPut, "/v1/users/:id", "/v1/users/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateUser - }, - http.StatusBadRequest, - "invalid user id", + }, http.StatusBadRequest, "invalid user id", }, { - "update user with no payload", - http.MethodPut, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "update user with no payload", http.MethodPut, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateUser - }, - http.StatusBadRequest, - "invalid user", + }, http.StatusBadRequest, "invalid user", }, { - "update user with bad payload", - http.MethodPut, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update user with bad payload", http.MethodPut, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateUser - }, - http.StatusBadRequest, - "invalid user", + }, http.StatusBadRequest, "invalid user", }, { - "update user with success", - http.MethodPut, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update user with error", http.MethodPut, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", sqlmock.AnyArg(), - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateUser - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "update user with error", - http.MethodPut, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{ + "update user with success", http.MethodPut, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","username":"username","password":"password"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", sqlmock.AnyArg(), - sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","username":"username","password":"password"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.UpdateUser - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -230,70 +118,27 @@ func TestUpdateUser(t *testing.T) { func TestDeleteUser(t *testing.T) { tests := []Test{ { - "delete user bad request", - http.MethodDelete, - "/v1/users/:id", - "/v1/users/bad-uuid", - []string{"id"}, - []string{"bad-uuid"}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete user bad request", http.MethodDelete, "/v1/users/:id", "/v1/users/bad-uuid", []string{"id"}, []string{"bad-uuid"}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteUser - }, - http.StatusBadRequest, - "invalid user id", + }, http.StatusBadRequest, "invalid user id", }, { - "delete user with success", - http.MethodDelete, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "users"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete user with error", http.MethodDelete, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM users`)). + WithArgs(pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteUser - }, - http.StatusNoContent, - "", + }, http.StatusInternalServerError, "some db error", }, { - "delete user with error", - http.MethodDelete, - "/v1/users/:id", - "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", - []string{"id"}, - []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "users"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "delete user with success", http.MethodDelete, "/v1/users/:id", "/v1/users/4d05b5f6-17c2-475e-87fe-3fc8b9567179", []string{"id"}, []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM users`)). + WithArgs(pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.DeleteUser - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusNoContent, "", }, } executeTests(t, tests) @@ -302,67 +147,40 @@ func TestDeleteUser(t *testing.T) { func TestGetUsers(t *testing.T) { tests := []Test{ { - "get users with empty table", - http.MethodGet, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(sqlmock.NewRows(userCols)) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get users with error", http.MethodGet, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUsers - }, - http.StatusOK, - "[]", + }, http.StatusInternalServerError, "some db error", }, { - "get users with 2 rows", - http.MethodGet, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(getMockedUserRows()) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get users with error in scanning", http.MethodGet, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(userCols). + AddRow("invalid", "name", "username", "password", "", "invalid", "invalid")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUsers - }, - http.StatusOK, - usersResponseBody, + }, http.StatusInternalServerError, "Scanning value error", }, { - "get users with error", - http.MethodGet, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{}, - nil, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "get users with empty table", http.MethodGet, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(userCols)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { + return handler.GetUsers + }, http.StatusOK, "[]", + }, + { + "get users with 2 rows", http.MethodGet, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{}, nil, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()).WillReturnRows(getMockedUserRows()) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.GetUsers - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusOK, usersResponseBody, }, } executeTests(t, tests) @@ -371,106 +189,50 @@ func TestGetUsers(t *testing.T) { func TestCreateUser(t *testing.T) { tests := []Test{ { - "create user with bad payload", - http.MethodPost, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{ + "create user with bad payload", http.MethodPost, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"bad":"request"}`), - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"bad":"request"}`), nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateUser - }, - http.StatusBadRequest, - "invalid user", + }, http.StatusBadRequest, "invalid user", }, { - "create user with no payload", - http.MethodPost, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{}, - nil, - nil, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + "create user with no payload", http.MethodPost, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{}, nil, nil, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateUser - }, - http.StatusBadRequest, - "invalid user", + }, http.StatusBadRequest, "invalid user", }, { - "create user with success", - http.MethodPost, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{ + "create user with error", http.MethodPost, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","username":"username","password":"password","features":"{\"albums\":true}"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "users"`)). - WithArgs(sqlmock.AnyArg(), "name", "username", sqlmock.AnyArg(), "{\"albums\":true}", sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","username":"username","password":"password","features":"{\"albums\":true}"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO users`)). + WithArgs(pgxmock.AnyArg(), "name", "username", pgxmock.AnyArg(), "{\"albums\":true}", pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateUser - }, - http.StatusCreated, - `"name":"name","username":"username"`, + }, http.StatusInternalServerError, "some db error", }, { - "create user with error", - http.MethodPost, - "/v1/users", - "/v1/users", - []string{}, - []string{}, - map[string]string{ + "create user with success", http.MethodPost, "/v1/users", "/v1/users", []string{}, []string{}, map[string]string{ echo.HeaderContentType: echo.MIMEApplicationJSON, - }, - strings.NewReader(`{"name":"name","username":"username","password":"password","features":"{\"albums\":true}"}`), - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "users"`)). - WithArgs(sqlmock.AnyArg(), "name", "username", sqlmock.AnyArg(), "{\"albums\":true}", sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - nil, - nil, - func(handler *Handler) func(ctx echo.Context) error { + }, strings.NewReader(`{"name":"name","username":"username","password":"password","features":"{\"albums\":true}"}`), func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO users`)). + WithArgs(pgxmock.AnyArg(), "name", "username", pgxmock.AnyArg(), "{\"albums\":true}", pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, nil, func(handler *Handler) func(ctx echo.Context) error { return handler.CreateUser - }, - http.StatusInternalServerError, - "some db error", + }, http.StatusCreated, `"name":"name","username":"username"`, }, } executeTests(t, tests) } -func getMockedUserRow() *sqlmock.Rows { - return sqlmock.NewRows(userCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", sampleTime, sampleTime) +func getMockedUserRow() *pgxmock.Rows { + return pgxmock.NewRows(userCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", "", sampleTime, sampleTime) } -func getMockedUserRows() *sqlmock.Rows { - return sqlmock.NewRows(userCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "name", "username", "password", sampleTime, sampleTime) +func getMockedUserRows() *pgxmock.Rows { + return pgxmock.NewRows(userCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", "", sampleTime, sampleTime). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "name", "username", "password", "", sampleTime, sampleTime) } diff --git a/api/internal/jobs/jobs.go b/api/internal/jobs/jobs.go deleted file mode 100644 index 8a45c24a..00000000 --- a/api/internal/jobs/jobs.go +++ /dev/null @@ -1,268 +0,0 @@ -package jobs - -import ( - "api/config" - "api/internal/models" - "api/pkg/services/worker" - "api/pkg/storage" - "context" - "errors" - "fmt" - "log/slog" - "strconv" - "strings" - "sync" - "time" - - "github.com/fsnotify/fsnotify" - uuid "github.com/satori/go.uuid" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -// Job ... -type Job struct { - Config *config.Config - DB *gorm.DB - Storage storage.Provider - Worker worker.WorkerClient -} - -func (j *Job) StartJobs() { - init := true - ticker := time.NewTicker(j.Config.Job.QueueInterval) - go func() { - for { - <-ticker.C - j.queueJob(init) - init = false - } - }() -} - -func (j *Job) queueJob(init bool) { - jobs := []models.Job{} - filter := "status='" + string(models.JobScheduled) + "'" - if init { - filter += " OR status='" + string(models.JobRunning) + "'" - } - result := j.DB.Clauses(clause.Locking{Strength: clause.LockingStrengthUpdate, Options: clause.LockingOptionsSkipLocked}).Model(&models.Job{}). - Where(filter). - Find(&jobs) - if result.Error != nil { - slog.Error("error getting jobs", "error", result.Error) - return - } - for _, job := range jobs { - go j.executeJob(job) - } -} - -func (j *Job) executeJob(jobCfg models.Job) { - slog.Info("starting job", "userId", jobCfg.UserID, "job", jobCfg.ID) - result := j.DB.Model(&models.Job{UserID: jobCfg.UserID, ID: jobCfg.ID}).Updates(map[string]interface{}{ - "Status": string(models.JobRunning), - }) - if result.Error != nil { - slog.Error("error updating job status", "error", result.Error) - return - } - var executorWg sync.WaitGroup - queue := make(chan models.MediaItem, j.Config.Job.Concurrency) - results := make(chan uuid.UUID) - for range j.Config.Job.Concurrency { - executorWg.Add(1) - go j.executeJobMediaItem(&executorWg, jobCfg, queue, results) - } - mediaItem, err := j.getJobMediaItem(jobCfg, uuid.Nil) - if err != nil { //nolint: nestif - j.updateJobStatus(jobCfg, models.JobCompleted) - } else { - queue <- mediaItem - for result := range results { - slog.Info("completed item from job queue", "mediaitem", result) - err := j.updateJobLastMediaItem(jobCfg, result) - if err != nil { - j.updateJobStatus(jobCfg, models.JobCompleted) - break - } - jobStatus := j.getJobStatus(jobCfg) - if jobStatus == models.JobRunning { - mediaItem, err := j.getJobMediaItem(jobCfg, result) - if err != nil { - j.updateJobStatus(jobCfg, models.JobCompleted) - break - } - queue <- mediaItem - } else { - slog.Info("stopping job", "userId", jobCfg.UserID, "job", jobCfg.ID, "status", jobStatus) - break - } - } - } - close(queue) - slog.Info("waiting for job to complete", "userId", jobCfg.UserID, "job", jobCfg.ID) - executorWg.Wait() - close(results) - slog.Info("completed job", "userId", jobCfg.UserID, "job", jobCfg.ID) -} - -func (j *Job) getJobMediaItem(jobCfg models.Job, lastMediaItemID uuid.UUID) (models.MediaItem, error) { - mediaItem := models.MediaItem{} - result := j.DB.Where("user_id=? AND id>?", jobCfg.UserID, lastMediaItemID).Order("created_at").First(&mediaItem) - if result.Error != nil { - if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return mediaItem, result.Error - } - slog.Error("error getting job mediaitem", "error", result.Error) - return mediaItem, result.Error - } - return mediaItem, nil -} - -func (j *Job) updateJobLastMediaItem(jobCfg models.Job, mediaItemID uuid.UUID) error { - result := j.DB.Model(&models.Job{UserID: jobCfg.UserID, ID: jobCfg.ID}).Updates(map[string]interface{}{ - "last_mediaitem_id": mediaItemID, - }) - if result.Error != nil { - slog.Error("error updating job last mediaitem", "error", result.Error) - return result.Error - } - return nil -} - -func (j *Job) executeJobMediaItem(wg *sync.WaitGroup, jobCfg models.Job, queue <-chan models.MediaItem, results chan<- uuid.UUID) { //nolint: cyclop - defer wg.Done() - watcher, err := fsnotify.NewWatcher() - if err != nil { - slog.Error("error creating file watcher", "error", err) - } - defer watcher.Close() - for item := range queue { - slog.Debug("processing item from job queue", "mediaitem", item.ID.String()) - // download the mediaitem to disk root depending on the type of the job - fileType, fileName := getFileTypeAndFileName(jobCfg.Components, item.ID.String(), string(item.MediaItemType)) - filePath := fmt.Sprintf("%s/%s", j.Config.Storage.DiskRoot, fileName) - err := j.Storage.Download(filePath, fileType, item.ID.String()) - if err != nil { - slog.Error("error downloading mediaitem for processing", "error", err) - continue - } - - // send to worker for processing - _, err = j.Worker.MediaItemProcess(context.Background(), &worker.MediaItemProcessRequest{ - UserId: item.UserID.String(), - Id: item.ID.String(), - FilePath: j.Config.Storage.DiskRoot, - Components: getComponents(jobCfg.Components), - Payload: j.getPayload(jobCfg, item), - }) - if err != nil { - slog.Error("error sending mediaitem for processing", "error", err) - continue - } - - // start a file watcher to notify when the file is removed - err = watcher.Add(filePath) - if err != nil { - slog.Error("error adding file to watcher", "file", fileName, "error", err) - } - - watcherLoop: - for { - select { - case event, ok := <-watcher.Events: - if !ok { - slog.Error("exiting watching file", "file", fileName) - break watcherLoop - } - if event.Op&fsnotify.Remove == fsnotify.Remove { - slog.Info("finished processing item from job queue", "mediaitem", item.ID.String()) - results <- item.ID - break watcherLoop - } - case err, ok := <-watcher.Errors: - if !ok { - slog.Error("error watching file", "file", fileName, "error", err) - break watcherLoop - } - } - } - } -} - -func getComponents(jobComponents string) []worker.MediaItemComponent { - components := strings.Split(jobComponents, ",") - workerComponents := []worker.MediaItemComponent{} - for _, component := range components { - switch strings.ToUpper(component) { - case worker.MediaItemComponent_METADATA.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_METADATA) - case worker.MediaItemComponent_PREVIEW_THUMBNAIL.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_PREVIEW_THUMBNAIL) - case worker.MediaItemComponent_PLACES.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_PLACES) - case worker.MediaItemComponent_CLASSIFICATION.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_CLASSIFICATION) - case worker.MediaItemComponent_FACES.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_FACES) - case worker.MediaItemComponent_OCR.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_OCR) - case worker.MediaItemComponent_SEARCH.String(): - workerComponents = append(workerComponents, worker.MediaItemComponent_SEARCH) - } - } - return workerComponents -} - -func (j *Job) getJobStatus(jobCfg models.Job) models.JobStatus { - currentJob := models.Job{} - result := j.DB.Model(&models.Job{}). - Where("id=? AND user_id=?", jobCfg.ID, jobCfg.UserID). - First(¤tJob) - if result.Error != nil { - slog.Error("error getting job status", "error", result.Error) - return "" - } - return currentJob.Status -} - -func (j *Job) updateJobStatus(jobCfg models.Job, status models.JobStatus) { - result := j.DB.Model(&models.Job{UserID: jobCfg.UserID, ID: jobCfg.ID}).Updates(map[string]interface{}{ - "Status": status, - }) - if result.Error != nil { - slog.Error("error updating job status", "error", result.Error) - } -} - -func (j *Job) getPayload(jobCfg models.Job, mediaItem models.MediaItem) map[string]string { - payload := map[string]string{} - _, fileName := getFileTypeAndFileName(jobCfg.Components, mediaItem.ID.String(), string(mediaItem.MediaItemType)) - payload["sourcePath"] = fmt.Sprintf("%s/%s", j.Config.Storage.DiskRoot, mediaItem.ID.String()) - payload["previewPath"] = fmt.Sprintf("%s/%s", j.Config.Storage.DiskRoot, fileName) - if mediaItem.Latitude != nil { - payload["latitude"] = strconv.FormatFloat(*mediaItem.Latitude, 'f', -1, 64) - } - if mediaItem.Longitude != nil { - payload["longitude"] = strconv.FormatFloat(*mediaItem.Longitude, 'f', -1, 64) - } - payload["mimeType"] = mediaItem.MimeType - payload["type"] = string(mediaItem.MediaItemType) - payload["exifdata"] = *mediaItem.EXIFData - if mediaItem.Keywords != nil { - payload["keywords"] = *mediaItem.Keywords - } - return payload -} - -func getFileTypeAndFileName(components, mediaItemID, mediaItemType string) (string, string) { - if strings.Contains(components, "metadata") { - return "originals", mediaItemID - } - suffix := "-preview" - if mediaItemType == "video" { - suffix += ".mp4" - } - return "previews", mediaItemID + suffix -} diff --git a/api/internal/jobs/jobs_test.go b/api/internal/jobs/jobs_test.go deleted file mode 100644 index 415ac457..00000000 --- a/api/internal/jobs/jobs_test.go +++ /dev/null @@ -1 +0,0 @@ -package jobs diff --git a/api/internal/middlewares/basicauth.go b/api/internal/middlewares/basicauth.go index 389a0571..3e1a43e2 100644 --- a/api/internal/middlewares/basicauth.go +++ b/api/internal/middlewares/basicauth.go @@ -11,9 +11,11 @@ func BasicAuthCheck(cfg *config.Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(ctx echo.Context) error { username, password, ok := ctx.Request().BasicAuth() - if ok && username == cfg.Admin.Username && password == cfg.Admin.Password { + if ok && username == cfg.Admin.Username && + password == cfg.Admin.Password { return next(ctx) } + return echo.ErrUnauthorized } } diff --git a/api/internal/middlewares/basicauth_test.go b/api/internal/middlewares/basicauth_test.go index 0a60de5b..ca57cbb2 100644 --- a/api/internal/middlewares/basicauth_test.go +++ b/api/internal/middlewares/basicauth_test.go @@ -1,21 +1,18 @@ package middlewares import ( + "api/config" + "api/internal/handlers" "net/http" "net/http/httptest" "regexp" "testing" "time" - "api/config" - "api/internal/handlers" - - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" "github.com/stretchr/testify/assert" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" + "github.com/stretchr/testify/require" ) var sampleTime, _ = time.Parse("2006-01-02 15:04:05 -0700", "2022-09-22 11:22:33 +0530") @@ -23,12 +20,10 @@ var sampleTime, _ = time.Parse("2006-01-02 15:04:05 -0700", "2022-09-22 11:22:33 func TestBasicAuthCheckUnauthorizedWithNoAuth(t *testing.T) { // handler cfg := &config.Config{Admin: config.Admin{ - Username: "test", - Password: "testT3st!", + Username: "test", Password: "testT3st!", }} handler := &handlers.Handler{ - Config: cfg, - DB: nil, + Config: cfg, DB: nil, } checkBasicAuth := BasicAuthCheck(cfg) @@ -44,12 +39,10 @@ func TestBasicAuthCheckUnauthorizedWithNoAuth(t *testing.T) { func TestBasicAuthCheckUnauthorizedWithBadAuth(t *testing.T) { // handler cfg := &config.Config{Admin: config.Admin{ - Username: "test", - Password: "testT3st!", + Username: "test", Password: "testT3st!", }} handler := &handlers.Handler{ - Config: cfg, - DB: nil, + Config: cfg, DB: nil, } checkBasicAuth := BasicAuthCheck(cfg) @@ -66,32 +59,22 @@ func TestBasicAuthCheckUnauthorizedWithBadAuth(t *testing.T) { func TestBasicAuthCheckOK(t *testing.T) { // handler cfg := &config.Config{Admin: config.Admin{ - Username: "test", - Password: "testT3st!", + Username: "test", Password: "testT3st!", }} // mock db // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) // handler handler := &handlers.Handler{ - Config: cfg, - DB: mockGDB, + Config: cfg, DB: mockDB, } - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)). - WillReturnRows(sqlmock.NewRows([]string{"id", "name", "username", "password", "created_at", "updated_at"}). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", sampleTime, sampleTime). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "name", "username", "password", sampleTime, sampleTime)) + mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(userCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "username", "password", "", sampleTime, sampleTime). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180", "name", "username", "password", "", sampleTime, sampleTime)) checkBasicAuth := BasicAuthCheck(cfg) // test diff --git a/api/internal/middlewares/feature.go b/api/internal/middlewares/feature.go index 0bb5e283..a6811e5a 100644 --- a/api/internal/middlewares/feature.go +++ b/api/internal/middlewares/feature.go @@ -3,9 +3,9 @@ package middlewares import ( "api/config" "api/internal/models" + "log/slog" "github.com/labstack/echo/v4" - "golang.org/x/exp/slog" ) // FeatureCheck ... @@ -15,19 +15,19 @@ func FeatureCheck(cfg *config.Config, feature string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(ctx echo.Context) error { features, _ := ctx.Get("features").(models.Features) - if (feature == "favourites" && cfg.Feature.Favourites && features.Favourites) || - (feature == "hidden" && cfg.Feature.Hidden && features.Hidden) || - (feature == "trash" && cfg.Feature.Trash && features.Trash) || - (feature == "albums" && cfg.Feature.Albums && features.Albums) || - (feature == "explore" && cfg.Feature.Explore && features.Explore) || + if (feature == "favourites" && cfg.Favourites && features.Favourites) || + (feature == "hidden" && cfg.Hidden && features.Hidden) || + (feature == "trash" && cfg.Trash && features.Trash) || + (feature == "albums" && cfg.Albums && features.Albums) || + (feature == "explore" && cfg.Explore && features.Explore) || (feature == "places" && cfg.Feature.Places && features.Places) || - (feature == "things" && cfg.Feature.Things && features.Things) || - (feature == "people" && cfg.Feature.People && features.People) || - (feature == "jobs" && cfg.Feature.Jobs && features.Jobs) || - (feature == "sharing" && cfg.Feature.Sharing) { + (feature == "people" && cfg.People && features.People) || + (feature == "jobs" && cfg.Jobs && features.Jobs) || + (feature == "sharing" && cfg.Sharing) { return next(ctx) } slog.Error("feature disabled or not accessible", "config", cfg.Feature, "features", features) + return echo.ErrForbidden } } diff --git a/api/internal/middlewares/feature_test.go b/api/internal/middlewares/feature_test.go index 54a820b5..a54e85e3 100644 --- a/api/internal/middlewares/feature_test.go +++ b/api/internal/middlewares/feature_test.go @@ -1,52 +1,49 @@ package middlewares import ( + "api/config" + "api/internal/handlers" + "api/internal/models" "encoding/json" "net/http" "net/http/httptest" "regexp" "testing" - "api/config" - "api/internal/handlers" - "api/internal/models" - - "github.com/DATA-DOG/go-sqlmock" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" "github.com/stretchr/testify/assert" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" + "github.com/stretchr/testify/require" +) + +var ( + userCols = []string{ + "id", "name", "username", "password", "features", "created_at", "updated_at", + } + albumCols = []string{ + "id", "user_id", "name", "description", "is_shared", "is_hidden", "mediaitems_count", + "cover_mediaitem_id", "created_at", "updated_at", + } + coverMediaItemCols = []string{ + "id", "user_id", "source_url", "preview_url", "thumbnail_url", "placeholder", "mediaitem_type", + "mediaitem_category", "width", "height", + } ) func TestFeatureCheckForbidden(t *testing.T) { // handler cfg := &config.Config{Feature: config.Feature{ - Albums: false, - Favourites: false, - Hidden: false, - Trash: false, - Explore: false, - Places: false, - Things: false, - People: false, - Sharing: false, - Jobs: false, + Albums: false, Favourites: false, Hidden: false, Trash: false, Explore: false, Places: false, + People: false, Sharing: false, Jobs: false, }} handler := &handlers.Handler{ - Config: cfg, - DB: nil, + Config: cfg, DB: nil, } featureHandlerMap := map[string]interface{}{ - "albums": handler.GetAlbums, - "favourites": handler.GetFavouriteMediaItems, - "hidden": handler.GetHiddenMediaItems, - "trash": handler.GetDeletedMediaItems, - "explore": handler.GetPlaces, - "places": handler.GetPlaces, - "things": handler.GetThings, - "people": handler.GetPeople, - "jobs": handler.GetJobs, + "albums": handler.GetAlbums, "favourites": handler.GetFavouriteMediaItems, + "hidden": handler.GetHiddenMediaItems, "trash": handler.GetDeletedMediaItems, + "explore": handler.GetPlaces, "places": handler.GetPlaces, + "people": handler.GetPeople, "jobs": handler.GetJobs, } for feature, handler := range featureHandlerMap { // test @@ -67,36 +64,17 @@ func TestFeatureCheckOK(t *testing.T) { }} // mock db // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) // handler handler := &handlers.Handler{ - Config: cfg, - DB: mockGDB, + Config: cfg, DB: mockDB, } - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(sqlmock.NewRows([]string{ - "id", "name", "description", "is_shared", "is_hidden", "cover_mediaitem_id", - "mediaitems_count", "created_at", "updated_at", - })) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{ - "id", "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "is_favourite", "is_hidden", "is_deleted", "status", "mediaitem_type", "mediaitem_category", - "width", - "height", "creation_time", "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "latitude", "longitude", "fps", "created_at", "updated_at", - })) + mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...))) featureHandlerMap := map[string]interface{}{ "albums": handler.GetAlbums, } diff --git a/api/internal/middlewares/jwt.go b/api/internal/middlewares/jwt.go index e1a1ebc7..862b182c 100644 --- a/api/internal/middlewares/jwt.go +++ b/api/internal/middlewares/jwt.go @@ -24,8 +24,10 @@ func JWTCheck(cfg *config.Config, cache cache.Provider) echo.MiddlewareFunc { var features models.Features _ = json.Unmarshal([]byte(claims.Features), &features) ctx.Set("features", features) + return next(ctx) } + return echo.ErrUnauthorized } } diff --git a/api/internal/middlewares/jwt_test.go b/api/internal/middlewares/jwt_test.go index 26901fa0..03664848 100644 --- a/api/internal/middlewares/jwt_test.go +++ b/api/internal/middlewares/jwt_test.go @@ -1,6 +1,11 @@ package middlewares import ( + "api/config" + "api/internal/auth" + "api/internal/handlers" + "api/internal/models" + "api/pkg/cache" "fmt" "net/http" "net/http/httptest" @@ -8,20 +13,12 @@ import ( "testing" "time" - "api/config" - "api/internal/auth" - "api/internal/handlers" - "api/internal/models" - "api/pkg/cache" - - "github.com/DATA-DOG/go-sqlmock" "github.com/bluele/gcache" "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v4" uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" + "github.com/stretchr/testify/require" ) func TestJWTCheckUnauthorizedWithNoToken(t *testing.T) { @@ -32,8 +29,7 @@ func TestJWTCheckUnauthorizedWithNoToken(t *testing.T) { // mock cache cache := &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} handler := &handlers.Handler{ - Config: cfg, - Cache: cache, + Config: cfg, Cache: cache, } checkJWT := JWTCheck(cfg, cache) @@ -54,8 +50,7 @@ func TestJWTCheckUnauthorizedWithBadToken(t *testing.T) { // mock cache cache := &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} handler := &handlers.Handler{ - Config: cfg, - Cache: cache, + Config: cfg, Cache: cache, } checkJWT := JWTCheck(cfg, cache) @@ -74,8 +69,7 @@ func TestJWTCheckOK(t *testing.T) { cfg := &config.Config{ Feature: config.Feature{ Albums: true, - }, - Auth: config.Auth{ + }, Auth: config.Auth{ AccessTTL: 60, }, } @@ -85,36 +79,17 @@ func TestJWTCheckOK(t *testing.T) { _ = cache.SetWithExpire(accessToken, nil, 1*time.Minute) // mock db // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) // handler handler := &handlers.Handler{ - Config: cfg, - DB: mockGDB, + Config: cfg, DB: mockDB, } - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "albums"`)). - WillReturnRows(sqlmock.NewRows([]string{ - "id", "name", "description", "is_shared", "is_hidden", "cover_mediaitem_id", - "mediaitems_count", "created_at", "updated_at", - })) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(sqlmock.NewRows([]string{ - "id", "filename", "description", "mime_type", "source_url", "preview_url", - "thumbnail_url", "is_favourite", "is_hidden", "is_deleted", "status", "mediaitem_type", "mediaitem_category", - "width", - "height", "creation_time", "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "latitude", "longitude", "fps", "created_at", "updated_at", - })) + mockDB.ExpectQuery(regexp.QuoteMeta(`SELECT a.*, m.id, m.user_id, m.source_url, m.preview_url, m.thumbnail_url, m.placeholder,`+ + ` m.mediaitem_type, m.mediaitem_category, m.width, m.height FROM albums`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(pgxmock.NewRows(append(albumCols, coverMediaItemCols...))) checkJWT := JWTCheck(cfg, cache) // test diff --git a/api/internal/models/album.go b/api/internal/models/album.go index f2fb8328..43ef1317 100644 --- a/api/internal/models/album.go +++ b/api/internal/models/album.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/jackc/pgx/v5" uuid "github.com/satori/go.uuid" ) @@ -10,21 +11,36 @@ const AlbumsTable = "albums" // Album ... type Album struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id"` - Name string `json:"name"` - Description *string `json:"description"` - IsShared *bool `json:"shared,omitempty" gorm:"column:is_shared;default:false"` - IsHidden *bool `json:"hidden,omitempty" gorm:"column:is_hidden;default:false"` - MediaItemsCount *int `json:"mediaItemsCount,omitempty" gorm:"column:mediaitems_count;default:0"` - CoverMediaItemID *uuid.UUID `json:"coverMediaItemId,omitempty" gorm:"column:cover_mediaitem_id;type:uuid"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - CoverMediaItem *MediaItem `json:"coverMediaItem" gorm:"references:ID"` - MediaItems []*MediaItem `json:"-" gorm:"many2many:album_mediaitems;references:ID;joinReferences:MediaitemID"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"userId"` + Name string `json:"name"` + Description *string `json:"description"` + IsShared *bool `json:"shared,omitempty"` + IsHidden *bool `json:"hidden,omitempty"` + MediaItemsCount *int `json:"mediaItemsCount,omitempty"` + CoverMediaItemID *uuid.UUID `json:"coverMediaItemId,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CoverMediaItem *CoverMediaItem `json:"coverMediaItem"` } // TableName ... func (Album) TableName() string { return AlbumsTable } + +func ScanRowsToAlbum(rows pgx.Rows) (Album, error) { + album := Album{} + coverMediaItem := &CoverMediaItem{} + + err := rows.Scan(&album.ID, &album.UserID, &album.Name, &album.Description, &album.IsShared, &album.IsHidden, + &album.MediaItemsCount, &album.CoverMediaItemID, &album.CreatedAt, &album.UpdatedAt, &coverMediaItem.ID, + &coverMediaItem.UserID, &coverMediaItem.SourceURL, &coverMediaItem.PreviewURL, + &coverMediaItem.ThumbnailURL, &coverMediaItem.Placeholder, &coverMediaItem.MediaItemType, + &coverMediaItem.MediaItemCategory, &coverMediaItem.Width, &coverMediaItem.Height) + if err == nil && album.CoverMediaItemID != nil { + album.CoverMediaItem = coverMediaItem + } + + return album, err +} diff --git a/api/internal/models/disk.go b/api/internal/models/disk.go index d9e8cf40..c178fdce 100644 --- a/api/internal/models/disk.go +++ b/api/internal/models/disk.go @@ -2,13 +2,11 @@ package models import ( "api/config" + "log/slog" "syscall" - - "golang.org/x/exp/slog" ) -type ( - // Disk ... +type ( // Disk ... Disk struct { Total uint64 `json:"total,omitempty"` Used uint64 `json:"used,omitempty"` @@ -19,15 +17,14 @@ type ( // GetDisk ... func GetDisk(cfg *config.Config) *Disk { diskStat := syscall.Statfs_t{} - err := syscall.Statfs(cfg.Storage.DiskRoot, &diskStat) + err := syscall.Statfs(cfg.DiskRoot, &diskStat) if err != nil { slog.Error("error getting disk stats", slog.Any("error", err)) + return nil } - disk := &Disk{ - Total: diskStat.Blocks * uint64(diskStat.Bsize), //nolint: gosec - Free: diskStat.Bfree * uint64(diskStat.Bsize), //nolint: gosec - } + disk := &Disk{Total: diskStat.Blocks * uint64(diskStat.Bsize), Free: diskStat.Bfree * uint64(diskStat.Bsize)} disk.Used = disk.Total - disk.Free + return disk } diff --git a/api/internal/models/disk_test.go b/api/internal/models/disk_test.go index 89ee4e81..ec81d430 100644 --- a/api/internal/models/disk_test.go +++ b/api/internal/models/disk_test.go @@ -1,9 +1,8 @@ package models import ( - "testing" - "api/config" + "testing" "github.com/stretchr/testify/assert" ) diff --git a/api/internal/models/features.go b/api/internal/models/features.go index 233cbd73..041f9e9e 100644 --- a/api/internal/models/features.go +++ b/api/internal/models/features.go @@ -2,8 +2,7 @@ package models import "api/config" -type ( - // Features ... +type ( // Features ... Features struct { Favourites bool `json:"favourites,omitempty"` Hidden bool `json:"hidden,omitempty"` @@ -11,7 +10,6 @@ type ( Albums bool `json:"albums,omitempty"` Explore bool `json:"explore,omitempty"` Places bool `json:"places,omitempty"` - Things bool `json:"things,omitempty"` People bool `json:"people,omitempty"` Sharing bool `json:"sharing,omitempty"` Jobs bool `json:"jobs,omitempty"` @@ -21,14 +19,8 @@ type ( // GetFeatures ... func GetFeatures(cfg *config.Config) *Features { return &Features{ - Favourites: cfg.Feature.Favourites, - Hidden: cfg.Feature.Hidden, - Trash: cfg.Feature.Trash, - Albums: cfg.Feature.Albums, - Explore: cfg.Feature.Explore, - Places: cfg.Feature.Places, - Things: cfg.Feature.Things, - People: cfg.Feature.People, - Sharing: cfg.Feature.Sharing, + Favourites: cfg.Favourites, Hidden: cfg.Hidden, Trash: cfg.Trash, + Albums: cfg.Albums, Explore: cfg.Explore, Places: cfg.Feature.Places, + People: cfg.People, Sharing: cfg.Sharing, } } diff --git a/api/internal/models/features_test.go b/api/internal/models/features_test.go index c90d1248..829f98ee 100644 --- a/api/internal/models/features_test.go +++ b/api/internal/models/features_test.go @@ -1,9 +1,8 @@ package models import ( - "testing" - "api/config" + "testing" "github.com/stretchr/testify/assert" ) @@ -16,39 +15,18 @@ var ( func TestGetFeatures(t *testing.T) { features := GetFeatures(&config.Config{}) assert.Equal(t, &Features{ - Albums: falseVal, - Favourites: falseVal, - Hidden: falseVal, - Trash: falseVal, - Explore: falseVal, - Places: falseVal, - People: falseVal, - Things: falseVal, - Sharing: falseVal, + Albums: falseVal, Favourites: falseVal, Hidden: falseVal, Trash: falseVal, + Explore: falseVal, Places: falseVal, People: falseVal, Sharing: falseVal, }, features) features = GetFeatures(&config.Config{ Feature: config.Feature{ - Albums: true, - Favourites: true, - Hidden: true, - Trash: true, - Explore: true, - Places: true, - People: true, - Things: true, - Sharing: true, + Albums: true, Favourites: true, Hidden: true, Trash: true, Explore: true, + Places: true, People: true, Sharing: true, }, }) assert.Equal(t, &Features{ - Albums: trueVal, - Favourites: trueVal, - Hidden: trueVal, - Trash: trueVal, - Explore: trueVal, - Places: trueVal, - People: trueVal, - Things: trueVal, - Sharing: trueVal, + Albums: trueVal, Favourites: trueVal, Hidden: trueVal, Trash: trueVal, + Explore: trueVal, Places: trueVal, People: trueVal, Sharing: trueVal, }, features) } diff --git a/api/internal/models/job.go b/api/internal/models/job.go index ad5e95f8..0c56b2a1 100644 --- a/api/internal/models/job.go +++ b/api/internal/models/job.go @@ -8,20 +8,18 @@ import ( const JobsTable = "jobs" -type ( - // JobStatus ... +type ( // JobStatus ... JobStatus string ) // Job ... type Job struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id"` - Status JobStatus `json:"status"` - Components string `json:"components"` - LastMediItemID *uuid.UUID `json:"lastMediaItemId,omitempty" gorm:"column:last_mediaitem_id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"userId"` + Status JobStatus `json:"status"` + Components []string `json:"components"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } const ( diff --git a/api/internal/models/mediaitem.go b/api/internal/models/mediaitem.go index 1e94ab38..2c2f4061 100644 --- a/api/internal/models/mediaitem.go +++ b/api/internal/models/mediaitem.go @@ -4,21 +4,21 @@ import ( "api/pkg/cache" "api/pkg/storage" "fmt" + "log/slog" "reflect" "strings" "sync" "time" + "github.com/jackc/pgx/v5" "github.com/pgvector/pgvector-go" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" "gorm.io/gorm" ) const MediaItemsTable = "mediaitems" -type ( - // MediaItemStatus ... +type ( // MediaItemStatus ... MediaItemStatus string // MediaItemType ... @@ -29,88 +29,93 @@ type ( // MediaitemEmbedding ... MediaitemEmbedding struct { - MediaitemID uuid.UUID `json:"-" gorm:"type:uuid"` - Embedding *pgvector.Vector `json:"-" gorm:"type:vector"` + MediaitemID uuid.UUID `json:"-"` + Embedding *pgvector.Vector `json:"-"` } // MediaitemFace ... MediaitemFace struct { - ID uuid.UUID `json:"-" gorm:"primaryKey;index:,unique;type:uuid"` - MediaitemID uuid.UUID `json:"-" gorm:"type:uuid"` - PeopleID *uuid.UUID `json:"-" gorm:"type:uuid"` - Embedding *pgvector.Vector `json:"-" gorm:"type:vector"` - Thumbnail string `json:"thumbnail" gorm:"column:thumbnail"` + ID uuid.UUID `json:"-"` + MediaitemID uuid.UUID `json:"-"` + PeopleID *uuid.UUID `json:"-"` + Embedding *pgvector.Vector `json:"-"` + Thumbnail string `json:"thumbnail"` } // MediaItem ... MediaItem struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id;uniqueIndex:idx_mediaitems_user_id_hash"` - Filename string `json:"filename"` - Hash *string `json:"hash,omitempty" gorm:"index:idx_mediaitems_user_id_hash"` - Description *string `json:"description,omitempty"` - MimeType string `json:"mimeType"` - SourceURL string `json:"sourceUrl" gorm:"column:source_url"` - PreviewURL string `json:"previewUrl" gorm:"column:preview_url"` - ThumbnailURL string `json:"thumbnailUrl" gorm:"column:thumbnail_url"` - Placeholder string `json:"placeholder" gorm:"column:placeholder"` - IsFavourite *bool `json:"favourite" gorm:"column:is_favourite;default:false"` - IsHidden *bool `json:"hidden" gorm:"column:is_hidden;default:false"` - IsDeleted *bool `json:"deleted" gorm:"column:is_deleted;default:false"` - Status MediaItemStatus `json:"status"` - MediaItemType MediaItemType `json:"mediaItemType" gorm:"column:mediaitem_type"` - MediaItemCategory MediaItemCategory `json:"mediaItemCategory" gorm:"column:mediaitem_category"` - Width int `json:"width"` - Height int `json:"height"` - CreationTime time.Time `json:"creationTime"` - CameraMake *string `json:"cameraMake,omitempty"` - CameraModel *string `json:"cameraModel,omitempty"` - FocalLength *string `json:"focalLength,omitempty"` - ApertureFnumber *string `json:"apertureFNumber,omitempty" gorm:"column:aperture_fnumber"` - IsoEquivalent *string `json:"isoEquivalent,omitempty"` - ExposureTime *string `json:"exposureTime,omitempty"` - Latitude *float64 `json:"latitude,omitempty"` - Longitude *float64 `json:"longitude,omitempty"` - FPS *string `json:"fps,omitempty"` - EXIFData *string `json:"-" gorm:"column:exif_data"` - Keywords *string `json:"-"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Embeddings []*MediaitemEmbedding `json:"-" gorm:"foreignKey:MediaitemID;references:ID"` - Faces []*MediaitemFace `json:"-" gorm:"foreignKey:MediaitemID;references:ID"` - Albums []*Album `json:"-" gorm:"many2many:album_mediaitems;foreignKey:ID;joinForeignKey:MediaitemID;references:ID;joinReferences:AlbumID"` - Places []*Place `json:"-" gorm:"many2many:place_mediaitems;foreignKey:ID;joinForeignKey:MediaitemID;references:ID;joinReferences:PlaceID"` - Things []*Thing `json:"-" gorm:"many2many:thing_mediaitems;foreignKey:ID;joinForeignKey:MediaitemID;references:ID;joinReferences:ThingID"` - People []*People `json:"-" gorm:"many2many:people_mediaitems;foreignKey:ID;joinForeignKey:MediaitemID;references:ID;joinReferences:PeopleID"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"userId"` + Filename string `json:"filename"` + Hash *string `json:"hash,omitempty"` + Description *string `json:"description,omitempty"` + MimeType string `json:"mimeType"` + SourceURL string `json:"sourceUrl"` + PreviewURL string `json:"previewUrl"` + ThumbnailURL string `json:"thumbnailUrl"` + Placeholder string `json:"placeholder"` + IsFavourite *bool `json:"favourite"` + IsHidden *bool `json:"hidden"` + IsDeleted *bool `json:"deleted"` + Status string `json:"status"` + MediaItemType string `json:"mediaItemType"` + MediaItemCategory string `json:"mediaItemCategory"` + Width int `json:"width"` + Height int `json:"height"` + CreationTime time.Time `json:"creationTime"` + CameraMake *string `json:"cameraMake,omitempty"` + CameraModel *string `json:"cameraModel,omitempty"` + FocalLength *string `json:"focalLength,omitempty"` + ApertureFnumber *string `json:"apertureFNumber,omitempty"` + IsoEquivalent *string `json:"isoEquivalent,omitempty"` + ExposureTime *string `json:"exposureTime,omitempty"` + Megapixels *string `json:"megapixels,omitempty"` + Latitude *float64 `json:"latitude,omitempty"` + Longitude *float64 `json:"longitude,omitempty"` + FPS *string `json:"fps,omitempty"` + EXIFData *string `json:"-"` + DetectedText *string `json:"-"` + Caption *string `json:"-"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } -) -const ( - Unspecified MediaItemStatus = "UNSPECIFIED" - Processing MediaItemStatus = "PROCESSING" - Ready MediaItemStatus = "READY" - Failed MediaItemStatus = "FAILED" - - Unknown MediaItemType = "unknown" - Photo MediaItemType = "photo" - Video MediaItemType = "video" - - Default MediaItemCategory = "default" - Screenshot MediaItemCategory = "screenshot" - Panorama MediaItemCategory = "panorama" - Slow MediaItemCategory = "slow" - Motion MediaItemCategory = "motion" - Live MediaItemCategory = "live" - Timelapse MediaItemCategory = "timelapse" - - preFetchTime = 24 + // CoverMediaItem ... + CoverMediaItem struct { + ID *uuid.UUID `json:"id"` + UserID *uuid.UUID `json:"userId"` + SourceURL *string `json:"sourceUrl"` + PreviewURL *string `json:"previewUrl"` + ThumbnailURL *string `json:"thumbnailUrl"` + Placeholder *string `json:"placeholder"` + MediaItemType *string `json:"mediaItemType"` + MediaItemCategory *string `json:"mediaItemCategory"` + Width *int `json:"width"` + Height *int `json:"height"` + } ) +const preFetchTime = 24 + // TableName ... func (MediaItem) TableName() string { return MediaItemsTable } +func ScanRowsToMediaItem(rows pgx.Rows) (MediaItem, error) { + mediaItem := MediaItem{} + err := rows.Scan(&mediaItem.ID, &mediaItem.UserID, &mediaItem.Filename, &mediaItem.Hash, &mediaItem.Description, + &mediaItem.MimeType, &mediaItem.SourceURL, &mediaItem.PreviewURL, &mediaItem.ThumbnailURL, + &mediaItem.Placeholder, &mediaItem.IsFavourite, &mediaItem.IsHidden, &mediaItem.IsDeleted, &mediaItem.Status, + &mediaItem.MediaItemType, &mediaItem.MediaItemCategory, &mediaItem.Width, &mediaItem.Height, + &mediaItem.CreationTime, &mediaItem.CameraMake, &mediaItem.CameraModel, &mediaItem.FocalLength, + &mediaItem.ApertureFnumber, &mediaItem.IsoEquivalent, &mediaItem.ExposureTime, &mediaItem.Megapixels, + &mediaItem.Latitude, &mediaItem.Longitude, &mediaItem.FPS, &mediaItem.EXIFData, &mediaItem.DetectedText, + &mediaItem.Caption, &mediaItem.CreatedAt, &mediaItem.UpdatedAt) + + return mediaItem, err +} + // MediaItemURLPlugin ... type MediaItemURLPlugin struct { Storage storage.Provider @@ -133,7 +138,8 @@ func (m *MediaItemURLPlugin) TransformMediaItemURL(gormDB *gorm.DB) { } } -func (m *MediaItemURLPlugin) transformMediaItemURL(wg *sync.WaitGroup, gormDB *gorm.DB, fieldName string) { //nolint: gocognit,cyclop +//nolint:gocognit,cyclop +func (m *MediaItemURLPlugin) transformMediaItemURL(wg *sync.WaitGroup, gormDB *gorm.DB, fieldName string) { defer wg.Done() field := gormDB.Statement.Schema.LookUpField(fieldName) if field != nil { //nolint: nestif @@ -142,10 +148,9 @@ func (m *MediaItemURLPlugin) transformMediaItemURL(wg *sync.WaitGroup, gormDB *g for i := range gormDB.Statement.ReflectValue.Len() { if fieldValue, isZero := field.ValueOf(gormDB.Statement.Context, gormDB.Statement.ReflectValue.Index(i)); !isZero { if val, ok := fieldValue.(string); ok { - err := field.Set(gormDB.Statement.Context, gormDB.Statement.ReflectValue.Index(i), - m.getMediaItemURL(fieldName, val)) + err := field.Set(gormDB.Statement.Context, gormDB.Statement.ReflectValue.Index(i), m.getMediaItemURL(fieldName, val)) if err != nil { - slog.Error("error setting %s value for %s: %+v", fieldName, val, err) + slog.Error("error setting field value", "field", fieldName, "value", val, "error", err) } } } @@ -153,10 +158,9 @@ func (m *MediaItemURLPlugin) transformMediaItemURL(wg *sync.WaitGroup, gormDB *g case reflect.Struct: if fieldValue, isZero := field.ValueOf(gormDB.Statement.Context, gormDB.Statement.ReflectValue); !isZero { if val, ok := fieldValue.(string); ok { - err := field.Set(gormDB.Statement.Context, gormDB.Statement.ReflectValue, - m.getMediaItemURL(fieldName, val)) + err := field.Set(gormDB.Statement.Context, gormDB.Statement.ReflectValue, m.getMediaItemURL(fieldName, val)) if err != nil { - slog.Error("error setting %s value for %s: %+v", fieldName, val, err) + slog.Error("error setting value for field", "field", fieldName, "value", val, "error", err) } } } @@ -182,6 +186,7 @@ func (m *MediaItemURLPlugin) getMediaItemURL(fieldName, filePath string) string fetchedURL, err := m.Storage.Get(fileType, fileID) if err != nil { slog.Error("error getting mediaitem url from storage", slog.Any("error", err)) + return "" } @@ -202,5 +207,6 @@ func getFileType(fieldName string) string { case "ThumbnailURL": return "thumbnails" } + return "unknown" } diff --git a/api/internal/models/mediaitem_test.go b/api/internal/models/mediaitem_test.go index a7d45136..3cba4270 100644 --- a/api/internal/models/mediaitem_test.go +++ b/api/internal/models/mediaitem_test.go @@ -52,36 +52,25 @@ func TestMediaItemURLPluginGetMediaItemURL(t *testing.T) { Args []string }{ { - "success getting from cache", - func() cache.Provider { - mockCache := &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} + "success getting from cache", func() cache.Provider { + mockCache := &cache.InMemoryCache{ + Connection: gcache.New(1024).LRU().Build(), + } mockCache.SetWithExpire("/originals/fileID", "cachedURL", 1*time.Minute) return mockCache - }, - nil, - "cachedURL", - []string{"SourceURL", "/originals/fileID"}, + }, nil, "cachedURL", []string{"SourceURL", "/originals/fileID"}, }, { - "error getting from storage", - func() cache.Provider { return &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} }, - &storage.Minio{Client: &mockMinioClient{wantErr: true}}, - "", - []string{"PreviewURL", "/previews/fileID"}, + "error getting from storage", func() cache.Provider { return &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} }, &storage.Minio{Client: &mockMinioClient{wantErr: true}}, "", []string{"PreviewURL", "/previews/fileID"}, }, { - "success getting from storage", - func() cache.Provider { return &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} }, - &storage.Minio{Client: &mockMinioClient{wantErr: false}}, - "https://minio/previews/fileID", - []string{"PreviewURL", "/previews/fileID"}, + "success getting from storage", func() cache.Provider { return &cache.InMemoryCache{Connection: gcache.New(1024).LRU().Build()} }, &storage.Minio{Client: &mockMinioClient{wantErr: false}}, "https://minio/previews/fileID", []string{"PreviewURL", "/previews/fileID"}, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { assert.Equal(t, test.ExpectedURL, (&MediaItemURLPlugin{ - Storage: test.MockStorage, - Cache: test.MockCache(), + Storage: test.MockStorage, Cache: test.MockCache(), }).getMediaItemURL(test.Args[0], test.Args[1])) }) } diff --git a/api/internal/models/models.go b/api/internal/models/models.go deleted file mode 100644 index 923d0d88..00000000 --- a/api/internal/models/models.go +++ /dev/null @@ -1,16 +0,0 @@ -package models - -// GetModels ... -func GetModels() []interface{} { - return []interface{}{ - User{}, - Album{}, - Place{}, - Thing{}, - People{}, - MediaItem{}, - MediaitemEmbedding{}, - MediaitemFace{}, - Job{}, - } -} diff --git a/api/internal/models/models_test.go b/api/internal/models/models_test.go deleted file mode 100644 index aaf90e6b..00000000 --- a/api/internal/models/models_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetModels(t *testing.T) { - models := GetModels() - assert.Len(t, models, 9) - assert.Equal(t, []interface{}{ - User{}, - Album{}, - Place{}, - Thing{}, - People{}, - MediaItem{}, - MediaitemEmbedding{}, - MediaitemFace{}, - Job{}, - }, models) -} diff --git a/api/internal/models/people.go b/api/internal/models/people.go index 617000dd..0284e19b 100644 --- a/api/internal/models/people.go +++ b/api/internal/models/people.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/jackc/pgx/v5" uuid "github.com/satori/go.uuid" ) @@ -10,20 +11,29 @@ const PeopleTable = "people" // People ... type People struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"userId"` Name string `json:"name"` - IsHidden *bool `json:"hidden" gorm:"column:is_hidden;default:false"` - CoverMediaItemID *uuid.UUID `json:"coverMediaItemId" gorm:"column:cover_mediaitem_id;type:uuid"` - CoverMediaItemFaceID *uuid.UUID `json:"coverMediaItemFaceId" gorm:"column:cover_mediaitem_face_id;type:uuid"` + IsHidden *bool `json:"hidden"` + CoverMediaItemID *uuid.UUID `json:"coverMediaItemId,omitempty"` + CoverMediaItemFaceID *uuid.UUID `json:"coverMediaItemFaceId,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` - CoverMediaItem *MediaItem `json:"coverMediaItem,omitempty" gorm:"references:ID"` - CoverMediaItemFace *MediaitemFace `json:"coverMediaItemFace" gorm:"foreignKey:CoverMediaItemFaceID;references:ID"` - MediaItems []*MediaItem `json:"-" gorm:"many2many:people_mediaitems;references:ID;joinReferences:MediaitemID"` + CoverMediaItem *MediaItem `json:"coverMediaItem,omitempty"` + CoverMediaItemFace *MediaitemFace `json:"coverMediaItemFace,omitempty"` } // TableName ... func (People) TableName() string { return PeopleTable } + +func ScanRowsToPerson(rows pgx.Rows) (People, error) { + person := People{CoverMediaItemFace: &MediaitemFace{}} + err := rows.Scan(&person.ID, &person.UserID, &person.Name, &person.IsHidden, &person.CoverMediaItemID, + &person.CoverMediaItemFaceID, &person.CreatedAt, &person.UpdatedAt, &person.CoverMediaItemFace.ID, + &person.CoverMediaItemFace.MediaitemID, &person.CoverMediaItemFace.PeopleID, + &person.CoverMediaItemFace.Embedding, &person.CoverMediaItemFace.Thumbnail) + + return person, err +} diff --git a/api/internal/models/place.go b/api/internal/models/place.go index 79b6e512..6994ce84 100644 --- a/api/internal/models/place.go +++ b/api/internal/models/place.go @@ -3,6 +3,7 @@ package models import ( "time" + "github.com/jackc/pgx/v5" uuid "github.com/satori/go.uuid" ) @@ -10,23 +11,38 @@ const PlaceTable = "places" // Place ... type Place struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id"` - Name string `json:"name"` - Postcode *string `json:"postcode"` - Town *string `json:"town"` - City *string `json:"city"` - State *string `json:"state"` - Country *string `json:"country"` - IsHidden *bool `json:"hidden" gorm:"column:is_hidden;default:false"` - CoverMediaItemID *uuid.UUID `json:"coverMediaItemId" gorm:"column:cover_mediaitem_id;type:uuid"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - CoverMediaItem *MediaItem `json:"coverMediaItem" gorm:"references:ID"` - MediaItems []*MediaItem `json:"-" gorm:"many2many:place_mediaitems;references:ID;joinReferences:MediaitemID"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"userId"` + Name string `json:"name"` + Postcode *string `json:"postcode"` + Country *string `json:"country"` + Locality *string `json:"locality"` + Area *string `json:"area"` + IsHidden *bool `json:"hidden"` + CoverMediaItemID *uuid.UUID `json:"coverMediaItemId,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CoverMediaItem *CoverMediaItem `json:"coverMediaItem,omitempty"` } // TableName ... func (Place) TableName() string { return PlaceTable } + +func ScanRowsToPlace(rows pgx.Rows) (Place, error) { + place := Place{} + coverMediaItem := &CoverMediaItem{} + + err := rows.Scan(&place.ID, &place.UserID, &place.Name, &place.Postcode, &place.Country, + &place.Locality, &place.Area, &place.IsHidden, &place.CoverMediaItemID, &place.CreatedAt, + &place.UpdatedAt, &coverMediaItem.ID, + &coverMediaItem.UserID, &coverMediaItem.SourceURL, &coverMediaItem.PreviewURL, + &coverMediaItem.ThumbnailURL, &coverMediaItem.Placeholder, &coverMediaItem.MediaItemType, + &coverMediaItem.MediaItemCategory, &coverMediaItem.Width, &coverMediaItem.Height) + if err == nil && place.CoverMediaItemID != nil { + place.CoverMediaItem = coverMediaItem + } + + return place, err +} diff --git a/api/internal/models/queue.go b/api/internal/models/queue.go new file mode 100644 index 00000000..b9881547 --- /dev/null +++ b/api/internal/models/queue.go @@ -0,0 +1,22 @@ +package models + +import ( + uuid "github.com/satori/go.uuid" +) + +const QueueTable = "queue" + +// Queue ... +type Queue struct { + ID uuid.UUID + UserID uuid.UUID + MediaItemID uuid.UUID + Type string + Components string + Status MediaItemStatus +} + +// TableName ... +func (Queue) TableName() string { + return QueueTable +} diff --git a/api/internal/models/queue_test.go b/api/internal/models/queue_test.go new file mode 100644 index 00000000..c447bda0 --- /dev/null +++ b/api/internal/models/queue_test.go @@ -0,0 +1,12 @@ +package models + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQueueTableName(t *testing.T) { + queue := Queue{} + assert.Equal(t, QueueTable, queue.TableName()) +} diff --git a/api/internal/models/thing.go b/api/internal/models/thing.go deleted file mode 100644 index 609453e6..00000000 --- a/api/internal/models/thing.go +++ /dev/null @@ -1,27 +0,0 @@ -package models - -import ( - "time" - - uuid "github.com/satori/go.uuid" -) - -const ThingTable = "things" - -// Thing ... -type Thing struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;index:,unique;type:uuid"` - UserID uuid.UUID `json:"userId" gorm:"column:user_id"` - Name string `json:"name"` - IsHidden *bool `json:"hidden" gorm:"column:is_hidden;default:false"` - CoverMediaItemID *uuid.UUID `json:"coverMediaItemId" gorm:"column:cover_mediaitem_id;type:uuid"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - CoverMediaItem *MediaItem `json:"coverMediaItem" gorm:"references:ID"` - MediaItems []*MediaItem `json:"-" gorm:"many2many:thing_mediaitems;references:ID;joinReferences:MediaitemID"` -} - -// TableName ... -func (Thing) TableName() string { - return ThingTable -} diff --git a/api/internal/models/thing_test.go b/api/internal/models/thing_test.go deleted file mode 100644 index ce172bb2..00000000 --- a/api/internal/models/thing_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestThingsTableName(t *testing.T) { - thing := Thing{} - assert.Equal(t, ThingTable, thing.TableName()) -} diff --git a/api/internal/models/user.go b/api/internal/models/user.go index c9c045af..547809df 100644 --- a/api/internal/models/user.go +++ b/api/internal/models/user.go @@ -10,9 +10,9 @@ const UsersTable = "users" // User ... type User struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"` + ID uuid.UUID `json:"id"` Name string `json:"name"` - Username string `json:"username" gorm:"unique"` + Username string `json:"username"` Password string `json:"-"` Features string `json:"-"` CreatedAt time.Time `json:"createdAt"` diff --git a/api/internal/models/version.go b/api/internal/models/version.go index b1d1b732..8cd38121 100644 --- a/api/internal/models/version.go +++ b/api/internal/models/version.go @@ -6,8 +6,7 @@ var ( DefaultGitSHA = "-" ) -type ( - // Version ... +type ( // Version ... Version struct { Version string `json:"version"` GitSHA string `json:"gitSha"` @@ -16,8 +15,5 @@ type ( // GetVersion ... func GetVersion() *Version { - return &Version{ - Version: DefaultVersion, - GitSHA: DefaultGitSHA, - } + return &Version{Version: DefaultVersion, GitSHA: DefaultGitSHA} } diff --git a/api/internal/server/grpc.go b/api/internal/server/grpc.go index af336951..fad8ff99 100644 --- a/api/internal/server/grpc.go +++ b/api/internal/server/grpc.go @@ -4,34 +4,29 @@ import ( "api/config" "api/internal/service" "api/pkg/services/api" + "context" "fmt" + "log/slog" "net" grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" "github.com/prometheus/client_golang/prometheus" - "golang.org/x/exp/slog" "google.golang.org/grpc" ) // StartGRPCServer ... func StartGRPCServer(cfg *config.Config, service *service.Service) *grpc.Server { - listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.GRPC.Host, cfg.GRPC.Port)) + listener, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", fmt.Sprintf("%s:%d", cfg.GRPC.Host, cfg.GRPC.Port)) if err != nil { panic(err) } - grpcMetrics := grpcprom.NewServerMetrics( - grpcprom.WithServerHandlingTimeHistogram( - grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), - ), - ) + grpcMetrics := grpcprom.NewServerMetrics(grpcprom.WithServerHandlingTimeHistogram(grpcprom.WithHistogramBuckets([]float64{ + 0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120, + }))) prometheus.DefaultRegisterer.MustRegister(grpcMetrics) - grpcServer := grpc.NewServer( - grpc.ChainUnaryInterceptor( - grpcMetrics.UnaryServerInterceptor(), - ), - ) + grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(grpcMetrics.UnaryServerInterceptor())) api.RegisterAPIServer(grpcServer, service) go func() { diff --git a/api/internal/server/http.go b/api/internal/server/http.go index c032a955..e8f65cd0 100644 --- a/api/internal/server/http.go +++ b/api/internal/server/http.go @@ -8,14 +8,15 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "strings" "time" "github.com/labstack/echo-contrib/echoprometheus" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus" - "golang.org/x/exp/slog" ) const httpTimeout = 10 @@ -27,21 +28,18 @@ func StartHTTPServer(handler *handlers.Handler) *http.Server { srvHandler := echo.New() //nolint:gosec httpServer := &http.Server{ - Addr: fmt.Sprintf("%s:%d", handler.Config.API.Host, handler.Config.API.Port), - Handler: srvHandler, + Addr: fmt.Sprintf("%s:%d", handler.Config.API.Host, handler.Config.API.Port), Handler: srvHandler, } // metrics srvHandler.Use(echoprometheus.NewMiddlewareWithConfig(echoprometheus.MiddlewareConfig{ - Namespace: "http", - Subsystem: "server", - Registerer: prometheus.DefaultRegisterer, - })) + Namespace: "http", Subsystem: "server", Registerer: prometheus.DefaultRegisterer, + }), middleware.CORS()) srvHandler.GET("/metrics", echoprometheus.NewHandler()) // file server - if handler.Config.Storage.Provider == "disk" { - fileRoute := getFileRoute(handler.Config.Storage.DiskRoot) + if handler.Config.Provider == "disk" { + fileRoute := getFileRoute(handler.Config.DiskRoot) slog.Info("starting file server on: " + fileRoute) - srvHandler.Static(fileRoute, handler.Config.Storage.DiskRoot) + srvHandler.Static(fileRoute, handler.Config.DiskRoot) } // routes srvHandler.GET("/version", handler.GetVersion) @@ -52,24 +50,14 @@ func StartHTTPServer(handler *handlers.Handler) *http.Server { version1.GET("/search", handler.Search, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) // mediaitems mediaItems := version1.Group("/mediaItems") - mediaItems.GET("/:id/places", handler.GetMediaItemPlaces, - getMiddlewareFuncs(handler.Config, handler.Cache, true, "places")...) - mediaItems.GET("/:id/things", handler.GetMediaItemThings, - getMiddlewareFuncs(handler.Config, handler.Cache, true, "things")...) - mediaItems.GET("/:id/people", handler.GetMediaItemPeople, - getMiddlewareFuncs(handler.Config, handler.Cache, true, "people")...) - mediaItems.GET("/:id/albums", handler.GetMediaItemAlbums, - getMiddlewareFuncs(handler.Config, handler.Cache, true, "albums")...) - mediaItems.GET("/:id", handler.GetMediaItem, - getMiddlewareFuncs(handler.Config, handler.Cache, true)...) - mediaItems.PUT("/:id", handler.UpdateMediaItem, - getMiddlewareFuncs(handler.Config, handler.Cache, true)...) - mediaItems.DELETE("/:id", handler.DeleteMediaItem, - getMiddlewareFuncs(handler.Config, handler.Cache, true)...) - mediaItems.GET("", handler.GetMediaItems, - getMiddlewareFuncs(handler.Config, handler.Cache, true)...) - mediaItems.POST("", handler.UploadMediaItems, - getMiddlewareFuncs(handler.Config, handler.Cache, true)...) + mediaItems.GET("/:id/places", handler.GetMediaItemPlaces, getMiddlewareFuncs(handler.Config, handler.Cache, true, "places")...) + mediaItems.GET("/:id/people", handler.GetMediaItemPeople, getMiddlewareFuncs(handler.Config, handler.Cache, true, "people")...) + mediaItems.GET("/:id/albums", handler.GetMediaItemAlbums, getMiddlewareFuncs(handler.Config, handler.Cache, true, "albums")...) + mediaItems.GET("/:id", handler.GetMediaItem, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) + mediaItems.PUT("/:id", handler.UpdateMediaItem, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) + mediaItems.DELETE("/:id", handler.DeleteMediaItem, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) + mediaItems.GET("", handler.GetMediaItems, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) + mediaItems.POST("", handler.UploadMediaItems, getMiddlewareFuncs(handler.Config, handler.Cache, true)...) // library favourites := version1.Group("/favourites") favourites.Use(getMiddlewareFuncs(handler.Config, handler.Cache, true, "favourites")...) @@ -95,14 +83,9 @@ func StartHTTPServer(handler *handlers.Handler) *http.Server { places.GET("/:id/mediaItems", handler.GetPlaceMediaItems) places.GET("/:id", handler.GetPlace) places.GET("", handler.GetPlaces) - things := explore.Group("/things") - things.Use(getMiddlewareFuncs(handler.Config, handler.Cache, true, "things")...) - things.GET("/:id/mediaItems", handler.GetThingMediaItems) - things.GET("/:id", handler.GetThing) - things.GET("", handler.GetThings) people := explore.Group("/people") people.Use(getMiddlewareFuncs(handler.Config, handler.Cache, true, "people")...) - people.GET("/:id/mediaItems", handler.GetPeopleMediaItems) + people.GET("/:id/mediaItems", handler.GetPersonMediaItems) people.GET("/:id", handler.GetPerson) people.PUT("/:id", handler.UpdatePerson) people.GET("", handler.GetPeople) @@ -171,10 +154,12 @@ func getMiddlewareFuncs(cfg *config.Config, cache cache.Provider, jwtCheck bool, for _, feature := range features { middlewareFuncs = append(middlewareFuncs, middlewares.FeatureCheck(cfg, feature)) } + return middlewareFuncs } func getFileRoute(storageDiskRoot string) string { fileRoute := strings.ReplaceAll(storageDiskRoot, "..", "") + return fileRoute } diff --git a/api/internal/server/http_test.go b/api/internal/server/http_test.go index 801aa5c7..91ce41d9 100644 --- a/api/internal/server/http_test.go +++ b/api/internal/server/http_test.go @@ -9,7 +9,9 @@ import ( ) func TestStartStopHTTPServer(t *testing.T) { - handler := &handlers.Handler{Config: &config.Config{Storage: config.Storage{Provider: "disk"}}} + handler := &handlers.Handler{ + Config: &config.Config{Storage: config.Storage{Provider: "disk"}}, + } srv := StartHTTPServer(handler) defer srv.Close() assert.NotNil(t, srv) @@ -25,28 +27,16 @@ func TestGetMiddlewareFuncs(t *testing.T) { ExpectedLen int }{ { - Name: "without jwt check and no features", - JWTCheck: false, - Features: []string{}, - ExpectedLen: 0, + Name: "without jwt check and no features", JWTCheck: false, Features: []string{}, ExpectedLen: 0, }, { - Name: "without jwt check and features", - JWTCheck: false, - Features: []string{"places", "favourites"}, - ExpectedLen: 2, + Name: "without jwt check and features", JWTCheck: false, Features: []string{"places", "favourites"}, ExpectedLen: 2, }, { - Name: "with jwt check and no features", - JWTCheck: true, - Features: []string{}, - ExpectedLen: 1, + Name: "with jwt check and no features", JWTCheck: true, Features: []string{}, ExpectedLen: 1, }, { - Name: "with jwt check and features", - JWTCheck: true, - Features: []string{"places", "favourites"}, - ExpectedLen: 3, + Name: "with jwt check and features", JWTCheck: true, Features: []string{"places", "favourites"}, ExpectedLen: 3, }, } for _, tc := range tests { diff --git a/api/internal/service/api.go b/api/internal/service/api.go index 899e8737..898a52a9 100644 --- a/api/internal/service/api.go +++ b/api/internal/service/api.go @@ -3,93 +3,225 @@ package service import ( "api/config" "api/internal/models" + "api/pkg/database" "api/pkg/services/api" - "api/pkg/services/worker" "api/pkg/storage" "context" "encoding/json" "errors" "fmt" + "log/slog" "os" "path/filepath" + "slices" "strings" "syscall" "time" + "github.com/jackc/pgx/v5" "github.com/pgvector/pgvector-go" uuid "github.com/satori/go.uuid" - "golang.org/x/exp/slog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "gorm.io/gorm" ) // Service ... type Service struct { api.UnimplementedAPIServer + Config *config.Config - DB *gorm.DB + DB database.DBInterface Storage storage.Provider + + enabledComponents *[]api.MediaItemComponent +} + +var errUploadingInvalidFilePath = errors.New("error uploading due to invalid file path") + +const ( + queryGetUsers = `SELECT id FROM users` + querySaveMediaItemMetadata = `UPDATE mediaitems SET creation_time=$3, camera_make=$4, camera_model=$5,` + + ` focal_length=$6, aperture_fnumber=$7, iso_equivalent=$8, exposure_time=$9, megapixels=$10, fps=$11,` + + ` latitude=$12, longitude=$13, exif_data=$14, mime_type=$15, mediaitem_type=$16, mediaitem_category=$17,` + + ` width=$18, height=$19, status=$20 WHERE user_id=$1 AND id=$2` + querySaveMediaItemPreviewThumbnail = `UPDATE mediaitems SET status=$3, source_url=$4, placeholder=$5,` + + ` preview_url=$6, thumbnail_url=$7 WHERE user_id=$1 AND id=$2` + querySavePlace = `INSERT INTO places (id, user_id, name, postcode, country,` + + ` locality, area, is_hidden, cover_mediaitem_id, created_at, updated_at) VALUES` + + ` ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (user_id, name, postcode)` + + ` DO UPDATE SET cover_mediaitem_id=$9, updated_at=$11` + querySaveMediaItemPlace = `INSERT INTO place_mediaitems (mediaitem_id, place_id)` + + ` VALUES ($1, $2) ON CONFLICT (mediaitem_id, place_id) DO NOTHING` + querySaveMediaItemFaces = `INSERT INTO mediaitem_faces (id, mediaitem_id,` + + ` people_id, embedding, thumbnail) VALUES ($1, $2, $3, $4, $5) ON CONFLICT` + + ` (mediaitem_id, people_id) DO UPDATE SET embedding=$4, thumbnail=$5` + queryGetMediaItemFaces = `SELECT id, mediaitem_id, people_id,` + + ` embedding FROM mediaitem_faces WHERE mediaitem_id IN` + + ` (SELECT id FROM mediaitems WHERE user_id=$1 AND status=$2)` + querySaveMediaItemFinalResult = `UPDATE mediaitems SET detected_text=$3, caption=$4` + + ` WHERE user_id=$1 AND id=$2` + querySaveMediaItemFinalResultEmbeddings = `INSERT INTO mediaitem_embeddings VALUES($1, $2)` + querySavePerson = `INSERT INTO people (id, user_id, name,` + + ` is_hidden, cover_mediaitem_id, cover_mediaitem_face_id, created_at, updated_at)` + + ` VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT(id) DO UPDATE cover_mediaitem_id=$5,` + + ` cover_mediaitem_face_id=$6, updated_at=$8` + querySaveMediaItemPeople = `INSERT INTO people_mediaitems (mediaitem_id, people_id)` + + ` VALUES ($1, $2) ON CONFLICT (mediaitem_id, people_id) DO NOTHING` + querySaveMediaItemFacePeople = `UPDATE mediaitem_faces SET people_id=$2 WHERE id=$1` + queryUnqueueMediaItem = `DELETE FROM queue WHERE id=$1` + queryGetMediaItemProcess = `UPDATE queue q SET status='PROCESSING' FROM mediaitems m WHERE` + + ` q.id=(SELECT id FROM queue WHERE status='UNSPECIFIED' ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1)` + + ` AND m.id = q.mediaitem_id RETURNING q.id, q.user_id, q.mediaitem_id, q.components,` + + ` m.mime_type, m.source_url, m.preview_url, m.mediaitem_type, m.mediaitem_category, m.latitude, m.longitude` +) + +func Init(cfg *config.Config, dbi database.DBInterface, storage storage.Provider) *Service { + enabledComponents := []api.MediaItemComponent{api.MediaItemComponent_METADATA} + if len(cfg.PreviewThumbnailParams) > 0 { + enabledComponents = append(enabledComponents, api.MediaItemComponent_PREVIEW_THUMBNAIL) + } + if cfg.ML.Places { + enabledComponents = append(enabledComponents, api.MediaItemComponent_PLACES) + } + if cfg.Classification { + enabledComponents = append(enabledComponents, api.MediaItemComponent_CLASSIFICATION) + } + if cfg.OCR { + enabledComponents = append(enabledComponents, api.MediaItemComponent_OCR) + } + if cfg.Search { + enabledComponents = append(enabledComponents, api.MediaItemComponent_SEARCH) + } + if cfg.Faces { + enabledComponents = append(enabledComponents, api.MediaItemComponent_FACES) + } + + return &Service{Config: cfg, DB: dbi, Storage: storage, enabledComponents: &enabledComponents} } func (s *Service) GetWorkerConfig(_ context.Context, _ *emptypb.Empty) (*api.ConfigResponse, error) { - type WorkerTask struct { + type Component struct { Name string `json:"name"` Source string `json:"source,omitempty"` Params string `json:"params,omitempty"` } - var workerTasks []WorkerTask - workerTasks = append(workerTasks, WorkerTask{Name: worker.MediaItemComponent_METADATA.String()}) - if len(s.Config.ML.PreviewThumbnailParams) > 0 { - workerTasks = append(workerTasks, WorkerTask{Name: worker.MediaItemComponent_PREVIEW_THUMBNAIL.String(), Params: s.Config.PreviewThumbnailParams}) + var components []Component + components = append(components, Component{Name: api.MediaItemComponent_METADATA.String()}) + if len(s.Config.PreviewThumbnailParams) > 0 { + components = append(components, Component{ + Name: api.MediaItemComponent_PREVIEW_THUMBNAIL.String(), Params: s.Config.PreviewThumbnailParams, + }) } if s.Config.ML.Places { - workerTasks = append(workerTasks, WorkerTask{Name: worker.MediaItemComponent_PLACES.String(), Source: s.Config.ML.PlacesProvider}) - } - if s.Config.ML.Classification { - workerTasks = append(workerTasks, WorkerTask{ - Name: worker.MediaItemComponent_CLASSIFICATION.String(), - Source: s.Config.ClassificationProvider, - Params: s.Config.ClassificationParams, + components = append(components, Component{ + Name: api.MediaItemComponent_PLACES.String(), Source: s.Config.PlacesProvider, }) } - if s.Config.ML.OCR { - workerTasks = append(workerTasks, WorkerTask{ - Name: worker.MediaItemComponent_OCR.String(), - Source: s.Config.OCRProvider, - Params: s.Config.OCRParams, + if s.Config.OCR { + components = append(components, Component{ + Name: api.MediaItemComponent_OCR.String(), Source: s.Config.OCRProvider, Params: s.Config.OCRParams, }) } - if s.Config.ML.Search { - workerTasks = append(workerTasks, WorkerTask{ - Name: worker.MediaItemComponent_SEARCH.String(), - Source: s.Config.SearchProvider, - Params: s.Config.SearchParams, + if s.Config.Search { + components = append(components, Component{ + Name: api.MediaItemComponent_SEARCH.String(), Source: s.Config.SearchProvider, Params: s.Config.SearchParams, }) } - if s.Config.ML.Faces { - workerTasks = append(workerTasks, WorkerTask{ - Name: worker.MediaItemComponent_FACES.String(), - Source: s.Config.FacesProvider, - Params: s.Config.FacesParams, + if s.Config.Faces { + components = append(components, Component{ + Name: api.MediaItemComponent_FACES.String(), Source: s.Config.FacesProvider, Params: s.Config.FacesParams, }) } - configBytes, err := json.Marshal(&workerTasks) + configBytes, err := json.Marshal(&components) if err != nil { return nil, status.Errorf(codes.Internal, "error parsing worker config: %s", err.Error()) } - return &api.ConfigResponse{ - Config: configBytes, + + return &api.ConfigResponse{Config: configBytes}, nil +} + +func (s *Service) GetMediaItemProcess(ctx context.Context, _ *emptypb.Empty) (*api.MediaItemProcessResponse, error) { //nolint:cyclop + var ( + queueID uuid.UUID + userID uuid.UUID + mediaItemID uuid.UUID + components string + mimeType *string + sourceURL string + previewURL *string + mediaItemType *string + mediaItemCategory *string + latitude *string + longitude *string + ) + err := s.DB.QueryRow(ctx, queryGetMediaItemProcess). + Scan(&queueID, &userID, &mediaItemID, &components, &mimeType, &sourceURL, &previewURL, &mediaItemType, + &mediaItemCategory, &latitude, &longitude) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return &api.MediaItemProcessResponse{}, nil + } + + slog.Error("error getting mediaitem to process", "error", err) + + return nil, status.Errorf(codes.Internal, "error getting mediaitem to process: %s", err.Error()) + } + + filteredComponents := []api.MediaItemComponent{} + queueComponents := strings.Split(components, ",") + for _, queueComponent := range queueComponents { + component := api.MediaItemComponent(api.MediaItemComponent_value[queueComponent]) + if slices.Contains(*s.enabledComponents, component) { + filteredComponents = append(filteredComponents, component) + } + } + + payload := map[string]string{"source_url": sourceURL} + if mimeType != nil { + payload["mime_type"] = *mimeType + } + if previewURL != nil { + payload["preview_url"] = *previewURL + } + if mediaItemType != nil { + payload["type"] = *mediaItemType + } + if mediaItemCategory != nil { + payload["category"] = *mediaItemCategory + } + if latitude != nil { + payload["latitude"] = *latitude + } + if longitude != nil { + payload["longitude"] = *longitude + } + + return &api.MediaItemProcessResponse{ + Id: queueID.String(), UserId: userID.String(), + MediaItemId: mediaItemID.String(), Components: filteredComponents, + Payload: payload, }, nil } -func (s *Service) GetUsers(_ context.Context, _ *emptypb.Empty) (*api.GetUsersResponse, error) { +func (s *Service) GetUsers(ctx context.Context, _ *emptypb.Empty) (*api.UsersResponse, error) { var userUUIDs []uuid.UUID - result := s.DB.Model(&models.User{}).Pluck("id", &userUUIDs) - if result.Error != nil { - slog.Error("error getting users", "error", result.Error) - return nil, status.Errorf(codes.Internal, "error getting users: %s", result.Error.Error()) + rows, err := s.DB.Query(ctx, queryGetUsers) + if err != nil { + slog.Error("error getting users", "error", err) + + return nil, status.Errorf(codes.Internal, "error getting users: %s", err.Error()) + } + + defer rows.Close() + for rows.Next() { + var userUUID uuid.UUID + if err := rows.Scan(&userUUID); err != nil { + slog.Error("error scanning user", "error", err) + + return nil, status.Errorf(codes.Internal, "error scanning user: %s", err.Error()) + } + userUUIDs = append(userUUIDs, userUUID) } users := []string{} @@ -97,67 +229,71 @@ func (s *Service) GetUsers(_ context.Context, _ *emptypb.Empty) (*api.GetUsersRe users = append(users, userUUID.String()) } - return &api.GetUsersResponse{ - Users: users, - }, nil + return &api.UsersResponse{Users: users}, nil } -func (s *Service) SaveMediaItemMetadata(_ context.Context, req *api.MediaItemMetadataRequest) (*emptypb.Empty, error) { +func (s *Service) SaveMediaItemMetadata(ctx context.Context, req *api.MediaItemMetadataRequest) (*emptypb.Empty, error) { userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - uid, err := uuid.FromString(req.Id) + mediaItemID, err := uuid.FromString(req.MediaItemId) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } - slog.Info("saving mediaitem metadata", "userId", req.UserId, "mediaitem", req.Id, "body", req.String()) + slog.Debug("saving mediaitem metadata", "user", req.UserId, "mediaitem", req.MediaItemId, "body", req.String()) creationTime := time.Now() if req.CreationTime != nil { creationTime, err = time.Parse("2006-01-02 15:04:05", *req.CreationTime) if err != nil { slog.Error("error getting mediaitem creation time", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem creation time") } } - mediaItem := models.MediaItem{ - UserID: userID, ID: uid, CreationTime: creationTime, - } + mediaItem := models.MediaItem{UserID: userID, ID: mediaItemID, CreationTime: creationTime} parseMediaItem(&mediaItem, req) - result := s.DB.Model(&mediaItem).Updates(mediaItem) - if result.Error != nil { - slog.Error("error updating mediaitem result", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error updating mediaitem result: %s", result.Error.Error()) + _, err = s.DB.Exec(ctx, querySaveMediaItemMetadata, userID, mediaItemID, mediaItem.CreationTime, mediaItem.CameraMake, + mediaItem.CameraModel, mediaItem.FocalLength, mediaItem.ApertureFnumber, mediaItem.IsoEquivalent, + mediaItem.ExposureTime, mediaItem.Megapixels, mediaItem.FPS, mediaItem.Latitude, mediaItem.Longitude, + mediaItem.EXIFData, mediaItem.MimeType, mediaItem.MediaItemType, mediaItem.MediaItemCategory, + mediaItem.Width, mediaItem.Height, mediaItem.Status) + if err != nil { + slog.Error("error saving mediaitem metadata", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error updating mediaitem result: %s", err.Error()) } - slog.Info("saved metadata for mediaitem", "mediaitem", mediaItem.ID.String()) + slog.Info("saved metadata for mediaitem", "user", req.UserId, "mediaitem", mediaItem.ID.String()) + return &emptypb.Empty{}, nil } -func (s *Service) SaveMediaItemPreviewThumbnail(_ context.Context, req *api.MediaItemPreviewThumbnailRequest) (*emptypb.Empty, error) { //nolint: cyclop +//nolint:cyclop +func (s *Service) SaveMediaItemPreviewThumbnail(ctx context.Context, req *api.MediaItemPreviewThumbnailRequest) (*emptypb.Empty, error) { userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - uid, err := uuid.FromString(req.Id) + mediaItemID, err := uuid.FromString(req.MediaItemId) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } - slog.Info("saving preview and thumbnail for mediaitem", "userId", req.UserId, "mediaitem", req.Id, "body", req.String()) - mediaItem := models.MediaItem{ - UserID: userID, ID: uid, - } - mediaItemUpdates := map[string]interface{}{ - "status": req.Status, - } + slog.Debug("saving preview and thumbnail for mediaitem", "user", req.UserId, "mediaitem", req.MediaItemId, "body", req.String()) + mediaItemUpdates := map[string]interface{}{"status": req.Status} if req.SourcePath != nil { - mediaItemUpdates["source_url"], err = uploadFile(s.Storage, *req.SourcePath, "originals", req.Id) + mediaItemUpdates["source_url"], err = uploadFile(s.Storage, *req.SourcePath, "originals", req.MediaItemId) if err != nil { - slog.Error("error uploading original file for mediaitem", "id", req.Id, "error", err) + slog.Error("error uploading original file for mediaitem", "id", req.MediaItemId, "error", err) + return &emptypb.Empty{}, status.Error(codes.Internal, "error uploading original file") } } @@ -165,181 +301,179 @@ func (s *Service) SaveMediaItemPreviewThumbnail(_ context.Context, req *api.Medi mediaItemUpdates["placeholder"] = *req.Placeholder } if req.PreviewPath != nil { - mediaItemUpdates["preview_url"], err = uploadFile(s.Storage, *req.PreviewPath, "previews", req.Id) + mediaItemUpdates["preview_url"], err = uploadFile(s.Storage, *req.PreviewPath, "previews", req.MediaItemId) if err != nil { - slog.Error("error uploading preview file for mediaitem", "id", req.Id, "error", err) + slog.Error("error uploading preview file for mediaitem", "id", req.MediaItemId, "error", err) + return &emptypb.Empty{}, status.Error(codes.Internal, "error uploading preview file") } } if req.ThumbnailPath != nil { - mediaItemUpdates["thumbnail_url"], err = uploadFile(s.Storage, *req.ThumbnailPath, "thumbnails", req.Id) + mediaItemUpdates["thumbnail_url"], err = uploadFile(s.Storage, *req.ThumbnailPath, "thumbnails", req.MediaItemId) if err != nil { - slog.Error("error uploading thumbnail file for mediaitem", "id", req.Id, "error", err) + slog.Error("error uploading thumbnail file for mediaitem", "id", req.MediaItemId, "error", err) + return &emptypb.Empty{}, status.Error(codes.Internal, "error uploading thumbnail file") } } - result := s.DB.Model(&mediaItem).Updates(mediaItemUpdates) - if result.Error != nil { - slog.Error("error updating mediaitem result", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error updating mediaitem result: %s", result.Error.Error()) + _, err = s.DB.Exec(ctx, querySaveMediaItemPreviewThumbnail, userID, mediaItemID, req.Status, + mediaItemUpdates["source_url"], mediaItemUpdates["placeholder"], mediaItemUpdates["preview_url"], + mediaItemUpdates["thumbnail_url"]) + if err != nil { + slog.Error("error saving mediaitem preview and thumbnail", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error updating mediaitem result: %s", err.Error()) } - slog.Info("saved preview and thumbnail for mediaitem", "mediaitem", mediaItem.ID.String()) + slog.Info("saved preview and thumbnail for mediaitem", "user", req.UserId, "mediaitem", req.MediaItemId) + return &emptypb.Empty{}, nil } -func (s *Service) SaveMediaItemPlace(_ context.Context, req *api.MediaItemPlaceRequest) (*emptypb.Empty, error) { +func (s *Service) SaveMediaItemPlace(ctx context.Context, req *api.MediaItemPlaceRequest) (*emptypb.Empty, error) { userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - uid, err := uuid.FromString(req.Id) + mediaItemID, err := uuid.FromString(req.MediaItemId) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } - slog.Info("saving mediaitem place", "userId", req.UserId, "mediaitem", req.Id, "body", req.String()) + slog.Debug("saving mediaitem place", "user", req.UserId, "mediaitem", req.MediaItemId, "body", req.String()) place := models.Place{ - UserID: userID, - Postcode: req.Postcode, - Town: req.Town, - City: req.City, - State: req.State, - Country: req.Country, + ID: uuid.NewV4(), + UserID: userID, Postcode: req.Postcode, Country: req.Country, Locality: req.Locality, Area: req.Area, } place.Name = getNameForPlace(place) - result := s.DB.Where(models.Place{UserID: userID, Name: place.Name, Postcode: place.Postcode}). - Attrs(models.Place{ID: uuid.NewV4()}). - Assign(models.Place{CoverMediaItemID: &uid}). - FirstOrCreate(&place) - if result.Error != nil { - slog.Error("error getting or creating place", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, - "error getting or creating place: %s", result.Error.Error()) - } - mediaItem := models.MediaItem{ID: uid} - err = s.DB.Omit("MediaItems.*").Model(&place).Association("MediaItems").Append(&mediaItem) + place.CreatedAt = time.Now() + place.UpdatedAt = place.CreatedAt + ptx, err := s.DB.Begin(ctx) if err != nil { - slog.Error("error saving mediaitem place", "error", err) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem place: %s", err.Error()) - } - slog.Info("saved place for mediaitem", "mediaitem", mediaItem.ID.String()) - return &emptypb.Empty{}, nil -} + slog.Error("error starting transaction for saving mediaitem place", "error", err) -func (s *Service) SaveMediaItemThing(_ context.Context, req *api.MediaItemThingRequest) (*emptypb.Empty, error) { - userID, err := uuid.FromString(req.UserId) - if err != nil { - slog.Error("error getting mediaitem user id", "error", err) - return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error starting transaction for saving mediaitem place: %s", err.Error()) } - uid, err := uuid.FromString(req.Id) + defer func() { + if err != nil { + _ = ptx.Rollback(ctx) + } + }() + _, err = ptx.Exec(ctx, querySavePlace, place.ID, userID, place.Name, place.Postcode, + place.Country, place.Locality, place.Area, false, mediaItemID, place.CreatedAt, place.UpdatedAt) if err != nil { - slog.Error("error getting mediaitem id", "error", err) - return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") + slog.Error("error saving place", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving place: %s", err.Error()) } - slog.Info("saving mediaitem thing", "userId", req.UserId, "mediaitem", req.Id, "body", req.String()) - thing := models.Thing{ - UserID: userID, - Name: req.Name, - } - result := s.DB.Where(models.Thing{UserID: userID, Name: thing.Name}). - Attrs(models.Thing{ID: uuid.NewV4()}). - Assign(models.Thing{CoverMediaItemID: &uid}). - FirstOrCreate(&thing) - if result.Error != nil { - slog.Error("error getting or creating thing", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, - "error getting or creating thing: %s", result.Error.Error()) - } - mediaItem := models.MediaItem{ID: uid} - err = s.DB.Omit("MediaItems.*").Model(&thing).Association("MediaItems").Append(&mediaItem) + _, err = ptx.Exec(ctx, querySaveMediaItemPlace, mediaItemID, place.ID) if err != nil { - slog.Error("error saving mediaitem thing", "error", err) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem thing: %s", err.Error()) + slog.Error("error saving mediaitem place", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem place: %s", err.Error()) + } + if err = ptx.Commit(ctx); err != nil { + slog.Error("error committing transaction for saving mediaitem place", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error committing transaction for saving mediaitem place: %s", err.Error()) } - slog.Info("saved thing for mediaitem", "mediaitem", mediaItem.ID.String()) + slog.Info("saved place for mediaitem", "user", req.UserId, "mediaitem", req.MediaItemId) + return &emptypb.Empty{}, nil } -func (s *Service) SaveMediaItemFaces(_ context.Context, req *api.MediaItemFacesRequest) (*emptypb.Empty, error) { - userID, err := uuid.FromString(req.UserId) +func (s *Service) SaveMediaItemFaces(ctx context.Context, req *api.MediaItemFacesRequest) (*emptypb.Empty, error) { + _, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - uid, err := uuid.FromString(req.Id) + mediaItemID, err := uuid.FromString(req.MediaItemId) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } - slog.Info("saving mediaitem faces", "userId", req.UserId, "mediaitem", req.Id) + slog.Debug("saving mediaitem faces", "user", req.UserId, "mediaitem", req.MediaItemId, "body", req.String()) mediaItemFaces := make([]models.MediaitemFace, len(req.GetEmbeddings())) faceThumbnails := req.GetThumbnails() for idx, reqEmbedding := range req.GetEmbeddings() { faceEmbedding := pgvector.NewVector(reqEmbedding.Embedding) mediaItemFaces[idx] = models.MediaitemFace{ - MediaitemID: uid, ID: uuid.NewV4(), - Embedding: &faceEmbedding, - Thumbnail: faceThumbnails[idx], + MediaitemID: mediaItemID, ID: uuid.NewV4(), Embedding: &faceEmbedding, Thumbnail: faceThumbnails[idx], } } - result := s.DB.Create(mediaItemFaces) - if result.Error != nil { - slog.Error("error saving mediaitem faces", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem faces: %s", result.Error.Error()) + for idx, mediaItemFace := range mediaItemFaces { + _, err = s.DB.Exec(ctx, querySaveMediaItemFaces, mediaItemFace.ID, mediaItemFace.MediaitemID, nil, mediaItemFace.Embedding, mediaItemFace.Thumbnail) + if err != nil { + slog.Error("error saving mediaitem faces", "idx", idx, "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem faces: %s", err.Error()) + } } - slog.Info("saved faces for mediaitem", "userId", userID.String(), "mediaitem", uid.String()) + slog.Info("saved faces for mediaitem", "user", req.UserId, "mediaitem", req.MediaItemId) + return &emptypb.Empty{}, nil } -func (s *Service) GetMediaItemFaceEmbeddings(_ context.Context, req *api.MediaItemFaceEmbeddingsRequest) (*api.MediaItemFaceEmbeddingsResponse, error) { +func (s *Service) GetMediaItemFaceEmbeddings(ctx context.Context, req *api.MediaItemFaceEmbeddingsRequest) (*api.MediaItemFaceEmbeddingsResponse, error) { userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - slog.Info("getting mediaitem face embeddings", "userId", req.UserId) + slog.Info("getting mediaitem face embeddings", "user", req.UserId) - mediaItems := []models.MediaItem{} - result := s.DB.Model(&models.MediaItem{}). - Where("status=? AND user_id=?", models.Ready, userID). - Preload("Faces"). - Find(&mediaItems) - if result.Error != nil { - slog.Error("error getting mediaitem face embeddings", "error", result.Error) - return nil, status.Errorf(codes.Internal, "error getting mediaitem face embeddings: %s", result.Error.Error()) + mediaItemFaces := []models.MediaitemFace{} + rows, err := s.DB.Query(ctx, queryGetMediaItemFaces, userID, api.MediaItemStatus_READY.String()) + if err != nil { + slog.Error("error getting mediaitem face embeddings", "error", err) + + return nil, status.Errorf(codes.Internal, "error getting mediaitem face embeddings: %s", err.Error()) + } + defer rows.Close() + for rows.Next() { + var mediaItemFace models.MediaitemFace + if err := rows.Scan(&mediaItemFace.ID, &mediaItemFace.MediaitemID, &mediaItemFace.PeopleID, &mediaItemFace.Embedding); err != nil { + slog.Error("error scanning mediaitem face embedding", "error", err) + + return nil, status.Errorf(codes.Internal, "error scanning mediaitem face embedding: %s", err.Error()) + } + mediaItemFaces = append(mediaItemFaces, mediaItemFace) } mediaItemFaceEmbeddings := []*api.MediaItemFaceEmbedding{} - for _, mediaItem := range mediaItems { - for _, mediaItemFace := range mediaItem.Faces { - mediaItemFaceEmbedding := &api.MediaItemFaceEmbedding{ - Id: mediaItemFace.ID.String(), - MediaItemId: mediaItemFace.MediaitemID.String(), - Embedding: &api.MediaItemEmbedding{Embedding: mediaItemFace.Embedding.Slice()}, - } - if mediaItemFace.PeopleID != nil { - mediaItemFaceEmbedding.PeopleId = mediaItemFace.PeopleID.String() - } - mediaItemFaceEmbeddings = append(mediaItemFaceEmbeddings, mediaItemFaceEmbedding) + for _, mediaItemFace := range mediaItemFaces { + mediaItemFaceEmbedding := &api.MediaItemFaceEmbedding{ + Id: mediaItemFace.ID.String(), MediaItemId: mediaItemFace.MediaitemID.String(), + } + if mediaItemFace.Embedding != nil { + mediaItemFaceEmbedding.Embedding = &api.MediaItemEmbedding{Embedding: mediaItemFace.Embedding.Slice()} + } + if mediaItemFace.PeopleID != nil { + mediaItemFaceEmbedding.PeopleId = mediaItemFace.PeopleID.String() } + mediaItemFaceEmbeddings = append(mediaItemFaceEmbeddings, mediaItemFaceEmbedding) } - return &api.MediaItemFaceEmbeddingsResponse{ - MediaItemFaceEmbeddings: mediaItemFaceEmbeddings, - }, nil + return &api.MediaItemFaceEmbeddingsResponse{MediaItemFaceEmbeddings: mediaItemFaceEmbeddings}, nil } -func (s *Service) SaveMediaItemPeople(_ context.Context, req *api.MediaItemPeopleRequest) (*emptypb.Empty, error) { //nolint:gocognit,funlen,cyclop +//nolint:gocognit,cyclop +func (s *Service) SaveMediaItemPeople(ctx context.Context, req *api.MediaItemPeopleRequest) (*emptypb.Empty, error) { userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - slog.Info("saving mediaitem people", "userId", req.UserId) + slog.Debug("saving mediaitem people", "user", req.UserId, "body", req.String()) peopleWithFaces := map[uuid.UUID][]uuid.UUID{} peopleWithMediaItems := map[uuid.UUID][]uuid.UUID{} @@ -348,25 +482,27 @@ func (s *Service) SaveMediaItemPeople(_ context.Context, req *api.MediaItemPeopl mediaItemID, err := uuid.FromString(reqMediaItem) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } for reqFace, reqPeople := range reqFacePeople.GetFacePeople() { faceID, err := uuid.FromString(reqFace) if err != nil { slog.Error("error getting face id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid face id") } peopleID, err := uuid.FromString(reqPeople) if err != nil { - slog.Warn("error getting people id", "faceId", reqPeople, "error", err) + slog.Warn("error getting people id", "faceId", faceID, "peopleId", reqPeople, "error", err) createdPeopleID, ok := peopleIdxUUIDs[reqPeople] if !ok { peopleID = uuid.NewV4() - peopleIdxUUIDs[reqPeople] = peopleID } else { peopleID = createdPeopleID } } + peopleIdxUUIDs[reqPeople] = peopleID _, idxOk := peopleWithFaces[peopleID] if idxOk { peopleWithFaces[peopleID] = append(peopleWithFaces[peopleID], faceID) @@ -382,95 +518,117 @@ func (s *Service) SaveMediaItemPeople(_ context.Context, req *api.MediaItemPeopl } } - for _, peopleID := range peopleIdxUUIDs { - mediaItems := []*models.MediaItem{} - for _, mediaItemID := range peopleWithMediaItems[peopleID] { - mediaItems = append(mediaItems, &models.MediaItem{ID: mediaItemID}) - } + for idx, peopleID := range peopleIdxUUIDs { defaultPeopleVisibility := false - defaultCoverMediaItemID := mediaItems[0].ID + defaultCoverMediaItemID := peopleWithMediaItems[peopleID][0] defaultCoverMediaItemFaceID := peopleWithFaces[peopleID][0] people := models.People{ - IsHidden: &defaultPeopleVisibility, - Name: "", - CoverMediaItemID: &defaultCoverMediaItemID, - CoverMediaItemFaceID: &defaultCoverMediaItemFaceID, + IsHidden: &defaultPeopleVisibility, Name: "", CoverMediaItemID: &defaultCoverMediaItemID, CoverMediaItemFaceID: &defaultCoverMediaItemFaceID, } - result := s.DB.Where(models.People{UserID: userID, ID: peopleID}). - Attrs(models.People{ID: peopleID}). - Assign(models.People{CoverMediaItemID: &defaultCoverMediaItemID}). - FirstOrCreate(&people) - if result.Error != nil { - slog.Error("error saving people", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving people: %s", result.Error.Error()) + people.CreatedAt = time.Now() + people.UpdatedAt = people.CreatedAt + ptx, err := s.DB.Begin(ctx) + if err != nil { + slog.Error("error starting transaction for saving mediaitem people", "idx", idx, "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error starting transaction for saving mediaitem people: %s", err.Error()) } - err = s.DB.Omit("MediaItems.*").Model(&people).Association("MediaItems").Append(mediaItems) + defer func() { + if err != nil { + _ = ptx.Rollback(ctx) + } + }() + _, err = ptx.Exec(ctx, querySavePerson, peopleID, userID, people.Name, people.IsHidden, people.CoverMediaItemID, people.CoverMediaItemFaceID, people.CreatedAt, people.UpdatedAt) if err != nil { - slog.Error("error saving people mediaitems", "error", err) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving people mediaitems: %s", err.Error()) + slog.Error("error saving people", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving people: %s", err.Error()) } - } + for midx, mediaItemID := range peopleWithMediaItems[peopleID] { + _, err = ptx.Exec(ctx, querySaveMediaItemPeople, mediaItemID, peopleID) + if err != nil { + slog.Error("error saving people mediaitems", "idx", idx, "mediaItemIdx", midx, "error", err) - for peopleID, faces := range peopleWithFaces { - result := s.DB.Model(&models.MediaitemFace{}).Where("id IN ?", faces).Updates(map[string]interface{}{ - "PeopleID": peopleID, - }) - if result.Error != nil { - slog.Error("error saving mediaitem faces people", "error", result.Error, "peopleId", peopleID, "faces", faces) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem faces people: %s", result.Error.Error()) + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving people mediaitems: %s", err.Error()) + } + } + for fidx, faceID := range peopleWithFaces[peopleID] { + _, err = ptx.Exec(ctx, querySaveMediaItemFacePeople, faceID, peopleID) + if err != nil { + slog.Error("error saving mediaitem faces", "idx", idx, "faceIdx", fidx, "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem faces: %s", err.Error()) + } + } + if err = ptx.Commit(ctx); err != nil { + slog.Error("error committing transaction for saving mediaitem people", "idx", idx, "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error committing transaction for saving mediaitem people: %s", err.Error()) } } - slog.Info("saved people for mediaitem", "userId", userID.String()) + slog.Info("saved people for mediaitem", "user", userID.String()) + return &emptypb.Empty{}, nil } -func (s *Service) SaveMediaItemFinalResult(_ context.Context, req *api.MediaItemFinalResultRequest) (*emptypb.Empty, error) { //nolint:cyclop,funlen,gocognit +//nolint:gocognit,cyclop +func (s *Service) SaveMediaItemFinalResult(ctx context.Context, req *api.MediaItemFinalResultRequest) (*emptypb.Empty, error) { + queueID, err := uuid.FromString(req.Id) + if err != nil { + slog.Error("error getting queue id", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid queue id") + } userID, err := uuid.FromString(req.UserId) if err != nil { slog.Error("error getting mediaitem user id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id") } - uid, err := uuid.FromString(req.Id) + mediaItemID, err := uuid.FromString(req.MediaItemId) if err != nil { slog.Error("error getting mediaitem id", "error", err) + return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "invalid mediaitem id") } - slog.Info("saving final mediaitem result", "userId", req.UserId, "mediaitem", req.Id) - - if len(req.GetKeywords()) > 0 { - mediaItem := models.MediaItem{} - mediaItem.ID = uid - mediaItem.UserID = userID - mediaItem.Keywords = &req.Keywords - result := s.DB.Model(&mediaItem).Updates(mediaItem) - if result.Error != nil { - slog.Error("error saving mediaitem keywords", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem final result: %s", result.Error.Error()) - } + slog.Debug("saving final mediaitem result", "user", req.UserId, "mediaitem", req.MediaItemId, "body", req.String()) + + _, err = s.DB.Exec(ctx, querySaveMediaItemFinalResult, userID, mediaItemID, req.DetectedText, req.Caption) + if err != nil { + slog.Error("error saving mediaitem final result", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem final result: %s", err.Error()) } if len(req.GetEmbeddings()) > 0 { - mediaItemEmbeddings := make([]models.MediaitemEmbedding, len(req.GetEmbeddings())) for idx, reqEmbedding := range req.GetEmbeddings() { mediaItemEmbedding := pgvector.NewVector(reqEmbedding.Embedding) - mediaItemEmbeddings[idx] = models.MediaitemEmbedding{MediaitemID: uid, Embedding: &mediaItemEmbedding} - } - result := s.DB.Create(mediaItemEmbeddings) - if result.Error != nil { - slog.Error("error saving mediaitem embeddings", "error", result.Error) - return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem final result: %s", result.Error.Error()) + _, err = s.DB.Exec(ctx, querySaveMediaItemFinalResultEmbeddings, mediaItemID, mediaItemEmbedding) + if err != nil { + slog.Error("error saving final result mediaitem embedding", "idx", idx, "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error saving mediaitem final result embedding: %s", err.Error()) + } } } + _, err = s.DB.Exec(ctx, queryUnqueueMediaItem, queueID) + if err != nil { + slog.Error("error unqueuing mediaitem from processing", "error", err) + + return &emptypb.Empty{}, status.Errorf(codes.Internal, "error unqueuing mediaitem from processing: %s", err.Error()) + } + defer func() { - err := filepath.WalkDir(s.Config.DiskRoot, func(path string, d os.DirEntry, err error) error { + err := filepath.WalkDir(s.Config.DiskRoot, func(path string, dir os.DirEntry, err error) error { if err != nil { - slog.Error("error iterating over directory for mediaitem", "mediaitem", req.Id, "error", err) + slog.Error("error iterating over directory for mediaitem", "mediaitem", req.MediaItemId, "error", err) + return err } - if !d.IsDir() && strings.Contains(d.Name(), req.Id) && filepath.Dir(path) == s.Config.DiskRoot { - // acquire lock to check if not copied + if !dir.IsDir() && strings.Contains(dir.Name(), req.MediaItemId) && + filepath.Dir(path) == s.Config.DiskRoot { // acquire lock to check if not copied for { slog.Debug("deleting file", "path", path) file, err := os.Open(path) @@ -481,42 +639,47 @@ func (s *Service) SaveMediaItemFinalResult(_ context.Context, req *api.MediaItem continue } if err = os.Remove(path); err != nil { - return fmt.Errorf("error removing file for mediaitem %s: %w", req.Id, err) + return fmt.Errorf("error removing file for mediaitem %s: %w", req.MediaItemId, err) } + break } } + return nil }) if err != nil { - slog.Error("error clearing the files for mediaitem", "mediaitem", req.Id, "error", err) + slog.Error("error clearing the files for mediaitem", "mediaitem", req.MediaItemId, "error", err) } else { - slog.Debug("cleared the files for mediaitem", "mediaitem", req.Id) + slog.Debug("cleared the files for mediaitem", "mediaitem", req.MediaItemId) } }() - slog.Info("saved final mediaitem result", "userId", userID.String(), "mediaitem", uid.String()) + slog.Info("saved final mediaitem result", "user", req.UserId, "mediaitem", req.MediaItemId) + return &emptypb.Empty{}, nil } func getNameForPlace(place models.Place) string { - if place.City != nil { - return *place.City + if place.Locality != nil { + return *place.Locality } - if place.Town != nil { - return *place.Town + if place.Area != nil { + return *place.Area } - return *place.State + + return *place.Country } func parseMediaItem(mediaItem *models.MediaItem, req *api.MediaItemMetadataRequest) { - mediaItem.Status = models.MediaItemStatus(req.Status) + mediaItem.Status = req.Status.String() mediaItem.CameraMake = req.CameraMake mediaItem.CameraModel = req.CameraModel mediaItem.FocalLength = req.FocalLength mediaItem.ApertureFnumber = req.ApertureFNumber mediaItem.IsoEquivalent = req.IsoEquivalent mediaItem.ExposureTime = req.ExposureTime + mediaItem.Megapixels = req.Megapixels mediaItem.FPS = req.Fps mediaItem.Latitude = req.Latitude mediaItem.Longitude = req.Longitude @@ -524,8 +687,8 @@ func parseMediaItem(mediaItem *models.MediaItem, req *api.MediaItemMetadataReque if req.MimeType != nil { mediaItem.MimeType = *req.MimeType } - mediaItem.MediaItemType = models.MediaItemType(req.Type) - mediaItem.MediaItemCategory = models.MediaItemCategory(req.Category) + mediaItem.MediaItemType = req.Type.String() + mediaItem.MediaItemCategory = req.Category.String() if req.Width != nil { mediaItem.Width = int(*req.Width) } @@ -536,7 +699,8 @@ func parseMediaItem(mediaItem *models.MediaItem, req *api.MediaItemMetadataReque func uploadFile(provider storage.Provider, filePath, fileType, fileID string) (string, error) { if len(filePath) == 0 { - return "", errors.New("error uploading due to invalid file path") + return "", errUploadingInvalidFilePath } + return provider.Upload(filePath, fileType, fileID) } diff --git a/api/internal/service/api_test.go b/api/internal/service/api_test.go index d7a8131d..e3a2c4ac 100644 --- a/api/internal/service/api_test.go +++ b/api/internal/service/api_test.go @@ -1,6 +1,9 @@ package service import ( + "api/config" + "api/pkg/services/api" + "api/pkg/storage" "context" "errors" "log" @@ -8,104 +11,86 @@ import ( "os" "regexp" "testing" - "time" - - "api/config" - "api/internal/models" - "api/pkg/services/api" - "api/pkg/storage" - "github.com/DATA-DOG/go-sqlmock" + "github.com/jackc/pgx/v5" + "github.com/pashagolub/pgxmock/v4" "github.com/pgvector/pgvector-go" + uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" "google.golang.org/protobuf/types/known/emptypb" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" ) var ( mimetype = "mimetype" - mediaitemType = "photo" - mediaitemCategory = "default" + mediaitemType = api.MediaItemType_PHOTO.String() + mediaitemCategory = api.MediaItemCategory_DEFAULT.String() + previewUrl = "preview_url" + sourceUrl = "source_url" + latitude = "latitude" + longitude = "longitude" + components = "METADATA,PLACES" badcreationtime = "bad-creation-time" creationtime = "2022-09-22 11:22:33" width int32 = 1080 height int32 = 720 - existingPlaceKeywords = "placecity placepostcode" placeholder = "placeholder" + sampleId = uuid.FromStringOrNil("4d05b5f6-17c2-475e-87fe-3fc8b9567179") + sampleEmbedding = pgvector.NewVector([]float32{0.42}) mediaItemResultRequest = api.MediaItemMetadataRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - MimeType: &mimetype, - Type: mediaitemType, - Category: mediaitemCategory, - Width: &width, - Height: &height, - CreationTime: &creationtime, + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + MimeType: &mimetype, Type: api.MediaItemType_PHOTO, Category: api.MediaItemCategory_DEFAULT, Width: &width, Height: &height, CreationTime: &creationtime, } mediaItemPreviewThumbnailRequest = api.MediaItemPreviewThumbnailRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Status: string(models.Ready), - Placeholder: &placeholder, + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + Status: api.MediaItemStatus_READY, Placeholder: &placeholder, + } + country = "country" + postcode = "postcode" + locality = "locality" + area = "area" + mediaItemEmbedding = api.MediaItemEmbedding{ + Embedding: []float32{0.0, 0.42, 0.111}, } - country = "country" - state = "state" - town = "town" - city = "city" - postcode = "postcode" - embedding = pgvector.NewVector([]float32{0.0, 0.42, 0.111}) - mediaItemEmbedding = api.MediaItemEmbedding{Embedding: []float32{0.0, 0.42, 0.111}} mediaItemPlaceRequest = api.MediaItemPlaceRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Country: &country, - State: &state, - Town: &town, - City: &city, - Postcode: &postcode, + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + Country: &country, Postcode: &postcode, Locality: &locality, Area: &area, } - mediaItemPlaceTownRequest = api.MediaItemPlaceRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Town: &town, + mediaItemPlaceLocalityRequest = api.MediaItemPlaceRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + Locality: &locality, } - mediaItemPlaceStateRequest = api.MediaItemPlaceRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - State: &state, - } - mediaItemThingRequest = api.MediaItemThingRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Name: "Pizza", + mediaItemPlaceAreaRequest = api.MediaItemPlaceRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + Area: &area, } mediaItemFacesRequest = api.MediaItemFacesRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Embeddings: []*api.MediaItemEmbedding{&mediaItemEmbedding}, - Thumbnails: []string{"thumbnail"}, + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + Embeddings: []*api.MediaItemEmbedding{&mediaItemEmbedding}, Thumbnails: []string{"thumbnail"}, } mediaItemPeopleRequest = api.MediaItemPeopleRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - MediaItemFacePeople: map[string]*api.MediaItemFacePeople{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemFacePeople: map[string]*api.MediaItemFacePeople{ "4d05b5f6-17c2-475e-87fe-3fc8b9567179": { - FacePeople: map[string]string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179": "4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - }, - "4d05b5f6-17c2-475e-87fe-3fc8b9567180": { - FacePeople: map[string]string{"4d05b5f6-17c2-475e-87fe-3fc8b9567180": "4d05b5f6-17c2-475e-87fe-3fc8b9567179"}, - }, - "4d05b5f6-17c2-475e-87fe-3fc8b9567181": { - FacePeople: map[string]string{"4d05b5f6-17c2-475e-87fe-3fc8b9567181": "1"}, - }, - "4d05b5f6-17c2-475e-87fe-3fc8b9567182": { - FacePeople: map[string]string{"4d05b5f6-17c2-475e-87fe-3fc8b9567182": "1"}, + FacePeople: map[string]string{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567179": "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + }, + }, "4d05b5f6-17c2-475e-87fe-3fc8b9567180": { + FacePeople: map[string]string{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567180": "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + }, + }, "4d05b5f6-17c2-475e-87fe-3fc8b9567181": { + FacePeople: map[string]string{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567181": "1", + }, + }, "4d05b5f6-17c2-475e-87fe-3fc8b9567182": { + FacePeople: map[string]string{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567182": "1", + }, }, }, } @@ -113,28 +98,11 @@ var ( UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", } mediaItemFinalResultRequest = api.MediaItemFinalResultRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Keywords: "some keywords", + Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + DetectedText: "some detected text", Caption: "some caption", Embeddings: []*api.MediaItemEmbedding{&mediaItemEmbedding}, } - sampleTime, _ = time.Parse("2006-01-02 15:04:05 -0700", "2022-09-22 11:22:33 +0530") - placeCols = []string{ - "id", "name", "postcode", "town", "city", "state", - "country", "cover_mediaitem_id", "is_hidden", "created_at", "updated_at", - } - thingCols = []string{ - "id", "name", "cover_mediaitem_id", "is_hidden", "created_at", "updated_at", - } - peopleCols = []string{ - "id", "name", "cover_mediaitem_id", "is_hidden", "created_at", "updated_at", - } - mediaitemCols = []string{ - "id", "user_id", "filename", "description", "mime_type", "keywords", "source_url", "preview_url", - "thumbnail_url", "is_favourite", "is_hidden", "is_deleted", "status", "mediaitem_type", "mediaitem_category", - "width", "height", "creation_time", "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "latitude", "longitude", "fps", "created_at", "updated_at", - } mediaitemFaceCols = []string{ "id", "mediaitem_id", "people_id", "embedding", } @@ -148,40 +116,29 @@ func TestGetWorkerConfig(t *testing.T) { ExpectedErr error }{ { - "get worker config with success", - &config.Config{ML: config.ML{Places: true, PlacesProvider: "openstreetmap"}}, - []byte(`[{"name":"METADATA"},{"name":"PLACES","source":"openstreetmap"}]`), - nil, + "get worker config with success", &config.Config{ + ML: config.ML{Places: true, PlacesProvider: "openstreetmap"}, + }, []byte(`[{"name":"METADATA"},{"name":"PLACES","source":"openstreetmap"}]`), nil, }, { - "get worker config with success with all config", - &config.Config{ML: config.ML{ - Places: true, PlacesProvider: "openstreetmap", - Classification: true, ClassificationProvider: "pytorch", ClassificationParams: `{"file":"model-file-name.pt"}`, - OCR: true, OCRProvider: "paddlepaddle", OCRParams: `{"det_model_dir":"/det_infer"}`, - Search: true, SearchProvider: "pytorch", SearchParams: `{"tokenizer_dir":"/tokenizer"}`, - Faces: true, FacesParams: `{"face_threshold":"0.9"}`, - PreviewThumbnailParams: `{"thumbnail_size":"512"}`, - }}, - []byte(`[{"name":"METADATA"},{"name":"PREVIEW_THUMBNAIL","params":"{\"thumbnail_size\":\"512\"}"},{"name":"PLACES","source":"openstreetmap"},` + - `{"name":"CLASSIFICATION","source":"pytorch","params":"{\"file\":\"model-file-name.pt\"}"},{"name":"OCR","source":"paddlepaddle",` + - `"params":"{\"det_model_dir\":\"/det_infer\"}"},{"name":"SEARCH","source":"pytorch","params":"{\"tokenizer_dir\":\"/tokenizer\"}"},` + - `{"name":"FACES","params":"{\"face_threshold\":\"0.9\"}"}]`), - nil, + "get worker config with success with all config", &config.Config{ML: config.ML{ + Places: true, PlacesProvider: "openstreetmap", OCR: true, OCRProvider: "paddlepaddle", + OCRParams: `{"det_model_dir":"/det_infer"}`, Search: true, SearchProvider: "pytorch", + SearchParams: `{"tokenizer_dir":"/tokenizer"}`, Faces: true, FacesParams: `{"face_threshold":"0.9"}`, + PreviewThumbnailParams: `{"thumbnail_size":"256"}`, + }}, []byte(`[{"name":"METADATA"},{"name":"PREVIEW_THUMBNAIL","params":"{\"thumbnail_size\":\"256\"}"},` + + `{"name":"PLACES","source":"openstreetmap"},{"name":"OCR","source":"paddlepaddle","params":` + + `"{\"det_model_dir\":\"/det_infer\"}"},{"name":"SEARCH","source":"pytorch","params":` + + `"{\"tokenizer_dir\":\"/tokenizer\"}"},{"name":"FACES","params":"{\"face_threshold\":\"0.9\"}"}]`), nil, }, { - "get worker config with no error", - &config.Config{}, - []byte(`[{"name":"METADATA"}]`), - nil, + "get worker config with no error", &config.Config{}, []byte(`[{"name":"METADATA"}]`), nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // service - service := &Service{ - Config: test.Config, - } + service := Init(test.Config, nil, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -197,57 +154,114 @@ func TestGetWorkerConfig(t *testing.T) { } } -func TestGetUsers(t *testing.T) { +func TestGetMediaItemProcess(t *testing.T) { tests := []struct { Name string - MockDB func(mock sqlmock.Sqlmock) - ExpectedResult *api.GetUsersResponse + MockDB func(mock pgxmock.PgxPoolIface) + ExpectedResult *api.MediaItemProcessResponse ExpectedErr error }{ { - "get users with success", - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "id" FROM "users"`)). - WillReturnRows(getMockedUserIDRows()) - }, - &api.GetUsersResponse{ - Users: []string{"4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567180"}, - }, - nil, + "get mediaitem to process with error", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`UPDATE queue`)). + WillReturnError(errors.New("some db error")) + }, nil, status.Error(codes.Internal, "error getting mediaitem to process: some db error"), }, { - "get users with error", - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT "id" FROM "users"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - status.Error(codes.Internal, "error getting users: some db error"), + "get mediaitem to process with error due to empty rows", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`UPDATE queue`)). + WillReturnRows(pgxmock.NewRows([]string{"id", "user_id", "mediaitem_id", "components", "mime_type", "source_url", + "preview_url", "mediaitem_type", "mediaitem_category", "latitude", "longitude"})) + }, &api.MediaItemProcessResponse{}, nil, + }, + { + "get mediaitem to process with success", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`UPDATE queue`)). + WillReturnRows(getMockedMediaItemToProcessRow()) + }, &api.MediaItemProcessResponse{ + Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567180", + MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567181", + Components: []api.MediaItemComponent{api.MediaItemComponent_METADATA, api.MediaItemComponent_PLACES}, + Payload: map[string]string{"category": mediaitemCategory, "latitude": "latitude", + "longitude": "longitude", "mime_type": "mimetype", + "preview_url": "preview_url", "source_url": "source_url", "type": mediaitemType}, + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, + service := Init(&config.Config{ML: config.ML{Places: true}}, mockDB, nil) + // server + ctx := context.Background() + conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(dialer(service))) + assert.Nil(t, err) + defer conn.Close() + client := api.NewAPIClient(conn) + res, err := client.GetMediaItemProcess(ctx, &emptypb.Empty{}) + // assert + assert.Equal(t, test.ExpectedErr, err) + if test.ExpectedErr == nil { + assert.Equal(t, test.ExpectedResult.Id, res.Id) + assert.Equal(t, test.ExpectedResult.UserId, res.UserId) + assert.Equal(t, test.ExpectedResult.MediaItemId, res.MediaItemId) + assert.Equal(t, test.ExpectedResult.Components, res.Components) + assert.Equal(t, test.ExpectedResult.Payload, res.Payload) + } + }) + } +} + +func TestGetUsers(t *testing.T) { + tests := []struct { + Name string + MockDB func(mock pgxmock.PgxPoolIface) + ExpectedResult *api.UsersResponse + ExpectedErr error + }{ + { + "get users with error", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id FROM users`)). + WillReturnError(errors.New("some db error")) + }, nil, status.Error(codes.Internal, "error getting users: some db error"), + }, + { + "get users with error due to scanning", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id FROM users`)). + WillReturnRows(getMockedUserIDRows(true)) + }, nil, status.Error(codes.Internal, "error scanning user: Scanning value error for column 'id': uuid: incorrect UUID length: invalid"), + }, + { + "get users with success", func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id FROM users`)). + WillReturnRows(getMockedUserIDRows(false)) + }, &api.UsersResponse{ + Users: []string{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567180", + }, + }, nil, + }, + } + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + // database + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) + defer mockDB.Close() + if test.MockDB != nil { + test.MockDB(mockDB) } + // service + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -266,88 +280,61 @@ func TestGetUsers(t *testing.T) { }) } } + func TestSaveMediaItemMetadata(t *testing.T) { tests := []struct { Name string Request *api.MediaItemMetadataRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedErr error }{ { - "save mediaitem result with invalid mediaitem user id", - &api.MediaItemMetadataRequest{UserId: "bad-mediaitem-user-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem result with invalid mediaitem user id", &api.MediaItemMetadataRequest{UserId: "bad-mediaitem-user-id"}, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem result with invalid mediaitem id", - &api.MediaItemMetadataRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem result with invalid mediaitem id", &api.MediaItemMetadataRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "bad-mediaitem-id", + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { - "save mediaitem result with incorrect creation time", - &api.MediaItemMetadataRequest{ - UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", CreationTime: &badcreationtime, - }, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem creation time"), + "save mediaitem result with incorrect creation time", &api.MediaItemMetadataRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", CreationTime: &badcreationtime, + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem creation time"), }, { - "save mediaitem result with success", - &mediaItemResultRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "mimetype", "photo", "default", 1080, 720, sqlmock.AnyArg(), sqlmock.AnyArg(), - "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + "save mediaitem result with error", &mediaItemResultRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error updating mediaitem result: some db error"), }, { - "save mediaitem result with error", - &mediaItemResultRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "mimetype", "photo", "default", 1080, 720, sqlmock.AnyArg(), sqlmock.AnyArg(), - "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error updating mediaitem result: some db error"), + "save mediaitem result with success", &mediaItemResultRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), + pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service tmpRoot := os.TempDir() - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - Storage: &storage.Disk{Root: tmpRoot}, - } + service := Init(&config.Config{}, mockDB, &storage.Disk{Root: tmpRoot}) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -366,62 +353,50 @@ func TestSaveMediaItemPreviewThumbnail(t *testing.T) { tests := []struct { Name string Request *api.MediaItemPreviewThumbnailRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) MockParams func(string) (string, string, string, func(), error) ExpectedErr error }{ { - "save mediaitem preview and thumbnail with invalid mediaitem user id", - &api.MediaItemPreviewThumbnailRequest{UserId: "bad-mediaitem-user-id"}, - nil, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem preview and thumbnail with invalid mediaitem user id", &api.MediaItemPreviewThumbnailRequest{ + UserId: "bad-mediaitem-user-id", + }, nil, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem preview and thumbnail with invalid mediaitem id", - &api.MediaItemPreviewThumbnailRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem preview and thumbnail with invalid mediaitem id", &api.MediaItemPreviewThumbnailRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "bad-mediaitem-id", + }, nil, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { "save mediaitem preview and thumbnail with error uploading original file", - &mediaItemPreviewThumbnailRequest, - nil, - func(tmpRoot string) (string, string, string, func(), error) { + &mediaItemPreviewThumbnailRequest, nil, func(tmpRoot string) (string, string, string, func(), error) { return "", "", "", func() {}, nil - }, - status.Errorf(codes.Internal, "error uploading original file"), + }, status.Errorf(codes.Internal, "error uploading original file"), }, { "save mediaitem preview and thumbnail with error uploading preview file", - &mediaItemPreviewThumbnailRequest, - nil, - func(tmpRoot string) (string, string, string, func(), error) { - os.Mkdir(tmpRoot+"/originals/", 0777) + &mediaItemPreviewThumbnailRequest, nil, func(tmpRoot string) (string, string, string, func(), error) { + os.Mkdir(tmpRoot+"/originals/", 0o777) originalFile, err := os.CreateTemp(tmpRoot, "original") if err != nil { return "", "", "", nil, err } - os.Mkdir(tmpRoot+"/previews/", 0777) + os.Mkdir(tmpRoot+"/previews/", 0o777) return originalFile.Name(), "", "", func() { defer os.Remove(tmpRoot + "/originals/") defer os.Remove(originalFile.Name()) }, nil - }, - status.Errorf(codes.Internal, "error uploading preview file"), + }, status.Errorf(codes.Internal, "error uploading preview file"), }, { "save mediaitem preview and thumbnail with error uploading thumbnail file", - &mediaItemPreviewThumbnailRequest, - nil, - func(tmpRoot string) (string, string, string, func(), error) { - os.Mkdir(tmpRoot+"/originals/", 0777) + &mediaItemPreviewThumbnailRequest, nil, func(tmpRoot string) (string, string, string, func(), error) { + os.Mkdir(tmpRoot+"/originals/", 0o777) originalFile, err := os.CreateTemp(tmpRoot, "original") if err != nil { return "", "", "", nil, err } - os.Mkdir(tmpRoot+"/previews/", 0777) + os.Mkdir(tmpRoot+"/previews/", 0o777) previewFile, err := os.CreateTemp(tmpRoot, "preview") if err != nil { return "", "", "", nil, err @@ -432,36 +407,25 @@ func TestSaveMediaItemPreviewThumbnail(t *testing.T) { defer os.Remove(tmpRoot + "/previews/") defer os.Remove(previewFile.Name()) }, nil - }, - status.Errorf(codes.Internal, "error uploading thumbnail file"), + }, status.Errorf(codes.Internal, "error uploading thumbnail file"), }, { - "save mediaitem preview and thumbnail with success", - &mediaItemPreviewThumbnailRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), - sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - func(tmpRoot string) (string, string, string, func(), error) { - os.Mkdir(tmpRoot+"/originals/", 0777) + "save mediaitem preview and thumbnail with error", &mediaItemPreviewThumbnailRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, func(tmpRoot string) (string, string, string, func(), error) { + os.Mkdir(tmpRoot+"/originals/", 0o777) originalFile, err := os.CreateTemp(tmpRoot, "original") if err != nil { return "", "", "", nil, err } - os.Mkdir(tmpRoot+"/previews/", 0777) + os.Mkdir(tmpRoot+"/previews/", 0o777) previewFile, err := os.CreateTemp(tmpRoot, "preview") - if err != nil { - return "", "", "", nil, err - } - os.Mkdir(tmpRoot+"/thumbnails/", 0777) + assert.NoError(t, err) + os.Mkdir(tmpRoot+"/thumbnails/", 0o777) thumbnailFile, err := os.CreateTemp(tmpRoot, "thumbnail") - if err != nil { - return "", "", "", nil, err - } + assert.NoError(t, err) return originalFile.Name(), previewFile.Name(), thumbnailFile.Name(), func() { defer os.Remove(tmpRoot + "/originals/") defer os.Remove(originalFile.Name()) @@ -470,32 +434,29 @@ func TestSaveMediaItemPreviewThumbnail(t *testing.T) { defer os.Remove(tmpRoot + "/thumbnails/") defer os.Remove(thumbnailFile.Name()) }, nil - }, - nil, + }, status.Error(codes.Internal, "error updating mediaitem result: some db error"), }, { - "save mediaitem preview and thumbnail with error", - &mediaItemPreviewThumbnailRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), - sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "4d05b5f6-17c2-475e-87fe-3fc8b9567179"). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - func(tmpRoot string) (string, string, string, func(), error) { - os.Mkdir(tmpRoot+"/originals/", 0777) + "save mediaitem preview and thumbnail with success", &mediaItemPreviewThumbnailRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + }, func(tmpRoot string) (string, string, string, func(), error) { + os.Mkdir(tmpRoot+"/originals/", 0o777) originalFile, err := os.CreateTemp(tmpRoot, "original") if err != nil { return "", "", "", nil, err } - os.Mkdir(tmpRoot+"/previews/", 0777) + os.Mkdir(tmpRoot+"/previews/", 0o777) previewFile, err := os.CreateTemp(tmpRoot, "preview") - assert.NoError(t, err) - os.Mkdir(tmpRoot+"/thumbnails/", 0777) + if err != nil { + return "", "", "", nil, err + } + os.Mkdir(tmpRoot+"/thumbnails/", 0o777) thumbnailFile, err := os.CreateTemp(tmpRoot, "thumbnail") - assert.NoError(t, err) + if err != nil { + return "", "", "", nil, err + } return originalFile.Name(), previewFile.Name(), thumbnailFile.Name(), func() { defer os.Remove(tmpRoot + "/originals/") defer os.Remove(originalFile.Name()) @@ -504,35 +465,21 @@ func TestSaveMediaItemPreviewThumbnail(t *testing.T) { defer os.Remove(tmpRoot + "/thumbnails/") defer os.Remove(thumbnailFile.Name()) }, nil - }, - status.Error(codes.Internal, "error updating mediaitem result: some db error"), + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service tmpRoot := os.TempDir() - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - Storage: &storage.Disk{Root: tmpRoot}, - } + service := Init(&config.Config{}, mockDB, &storage.Disk{Root: tmpRoot}) // mock tmp params if test.MockParams != nil { originalPath, previewPath, thumbnailPath, clear, err := test.MockParams(tmpRoot) @@ -560,254 +507,101 @@ func TestSaveMediaItemPlace(t *testing.T) { tests := []struct { Name string Request *api.MediaItemPlaceRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedErr error }{ { - "save mediaitem place with invalid mediaitem user id", - &api.MediaItemPlaceRequest{UserId: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem place with invalid mediaitem user id", &api.MediaItemPlaceRequest{UserId: "bad-mediaitem-id"}, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem place with invalid mediaitem id", - &api.MediaItemPlaceRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem place with invalid mediaitem id", &api.MediaItemPlaceRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "bad-mediaitem-id", + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { - "save mediaitem place with city success", - &mediaItemPlaceRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "place_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow(nil)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - }, - { - "save mediaitem place with town success", - &mediaItemPlaceTownRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "place_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow(nil)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + "save mediaitem place with error starting transaction", &mediaItemPlaceRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}).WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error starting transaction for saving mediaitem place: some db error"), }, { - "save mediaitem place with state success", - &mediaItemPlaceStateRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "place_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow(nil)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, - }, - { - "save mediaitem place with place find or create error", - &mediaItemPlaceRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). + "save mediaitem place with error saving place", &mediaItemPlaceRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - }, - status.Error(codes.Internal, "error getting or creating place: some db error"), + }, status.Error(codes.Internal, "error saving place: some db error"), }, { - "save mediaitem place with error", - &mediaItemPlaceRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "places"`)). - WillReturnRows(getMockedPlaceRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "places"`)). + "save mediaitem place with error saving mediaitem place", &mediaItemPlaceRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem place: some db error"), - }, - } - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) - defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) - if test.MockDB != nil { - test.MockDB(mock) - } - // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } - // server - ctx := context.Background() - conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(dialer(service))) - assert.Nil(t, err) - defer conn.Close() - client := api.NewAPIClient(conn) - _, err = client.SaveMediaItemPlace(ctx, test.Request) - // assert - assert.Equal(t, test.ExpectedErr, err) - }) - } -} - -func TestSaveMediaItemThing(t *testing.T) { - tests := []struct { - Name string - Request *api.MediaItemThingRequest - MockDB func(mock sqlmock.Sqlmock) - ExpectedErr error - }{ - { - "save mediaitem thing with invalid mediaitem user id", - &api.MediaItemThingRequest{UserId: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + }, status.Error(codes.Internal, "error saving mediaitem place: some db error"), }, { - "save mediaitem thing with invalid mediaitem id", - &api.MediaItemThingRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem place with error committing transaction", &mediaItemPlaceRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectCommit().WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error committing transaction for saving mediaitem place: some db error"), }, { - "save mediaitem thing with success", - &mediaItemThingRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "thing_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) + "save mediaitem place with all details success", &mediaItemPlaceRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) mock.ExpectCommit() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow(&existingPlaceKeywords)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + }, nil, }, { - "save mediaitem thing with thing find or create error", - &mediaItemThingRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnError(errors.New("some db error")) - }, - status.Error(codes.Internal, "error getting or creating thing: some db error"), + "save mediaitem place with locality success", &mediaItemPlaceLocalityRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectCommit() + }, nil, }, { - "save mediaitem thing with error", - &mediaItemThingRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "things"`)). - WillReturnRows(getMockedThingRow()) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) + "save mediaitem place with area success", &mediaItemPlaceAreaRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO places`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO place_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "things"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem thing: some db error"), + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -815,7 +609,7 @@ func TestSaveMediaItemThing(t *testing.T) { assert.Nil(t, err) defer conn.Close() client := api.NewAPIClient(conn) - _, err = client.SaveMediaItemThing(ctx, test.Request) + _, err = client.SaveMediaItemPlace(ctx, test.Request) // assert assert.Equal(t, test.ExpectedErr, err) }) @@ -826,67 +620,43 @@ func TestSaveMediaItemFaces(t *testing.T) { tests := []struct { Name string Request *api.MediaItemFacesRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedErr error }{ { - "save mediaitem faces with invalid mediaitem user id", - &api.MediaItemFacesRequest{UserId: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem faces with invalid mediaitem user id", &api.MediaItemFacesRequest{UserId: "bad-mediaitem-id"}, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem faces with invalid mediaitem id", - &api.MediaItemFacesRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem faces with invalid mediaitem id", &api.MediaItemFacesRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemId: "bad-mediaitem-id", + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { - "save mediaitem faces with success", - &mediaItemFacesRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitem_faces"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + "save mediaitem faces with error", &mediaItemFacesRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error saving mediaitem faces: some db error"), }, { - "save mediaitem faces with error", - &mediaItemFacesRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitem_faces"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem faces: some db error"), + "save mediaitem faces with success", &mediaItemFacesRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -905,70 +675,54 @@ func TestGetMediaItemFaceEmbeddings(t *testing.T) { tests := []struct { Name string Request *api.MediaItemFaceEmbeddingsRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedResult *api.MediaItemFaceEmbeddingsResponse ExpectedErr error }{ { - "get mediaitem face embeddings with invalid mediaitem user id", - &api.MediaItemFaceEmbeddingsRequest{UserId: "bad-mediaitem-user-id"}, - nil, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "get mediaitem face embeddings with invalid mediaitem user id", &api.MediaItemFaceEmbeddingsRequest{ + UserId: "bad-mediaitem-user-id", + }, nil, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "get mediaitem face embeddings with success", - &mediaItemFaceEmbeddingsRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnRows(getMockedMediaItemRow(&existingPlaceKeywords)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitem_faces"`)). - WillReturnRows(getMockedMediaItemFaceEmbeddingRows()) - }, - &api.MediaItemFaceEmbeddingsResponse{ + "get mediaitem face embeddings with error", &mediaItemFaceEmbeddingsRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, mediaitem_id, people_id, embedding FROM mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, nil, status.Error(codes.Internal, "error getting mediaitem face embeddings: some db error"), + }, + { + "get mediaitem face embeddings with error due to scanning", &mediaItemFaceEmbeddingsRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, mediaitem_id, people_id, embedding FROM mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemFaceEmbeddingRows(true)) + }, nil, status.Error(codes.Internal, "error scanning mediaitem face embedding: Scanning value error for column 'id': uuid: incorrect UUID length: invalid"), + }, + { + "get mediaitem face embeddings with success", &mediaItemFaceEmbeddingsRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, mediaitem_id, people_id, embedding FROM mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnRows(getMockedMediaItemFaceEmbeddingRows(false)) + }, &api.MediaItemFaceEmbeddingsResponse{ MediaItemFaceEmbeddings: []*api.MediaItemFaceEmbedding{ { - MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - Embedding: &mediaItemEmbedding, + MediaItemId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Embedding: nil, }, }, - }, - nil, - }, - { - "get mediaitem face embeddings with error", - &mediaItemFaceEmbeddingsRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "mediaitems"`)). - WillReturnError(errors.New("some db error")) - }, - nil, - status.Error(codes.Internal, "error getting mediaitem face embeddings: some db error"), + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -981,7 +735,6 @@ func TestGetMediaItemFaceEmbeddings(t *testing.T) { assert.Equal(t, test.ExpectedErr, err) if test.ExpectedResult != nil { for idx, mediaItemFaceEmbedding := range test.ExpectedResult.MediaItemFaceEmbeddings { - assert.Equal(t, mediaItemFaceEmbedding.Embedding.Embedding, res.MediaItemFaceEmbeddings[idx].Embedding.Embedding) assert.Equal(t, mediaItemFaceEmbedding.MediaItemId, res.MediaItemFaceEmbeddings[idx].MediaItemId) } } else { @@ -995,135 +748,142 @@ func TestSaveMediaItemPeople(t *testing.T) { tests := []struct { Name string Request *api.MediaItemPeopleRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedErr error }{ { - "save mediaitem people with invalid mediaitem user id", - &api.MediaItemPeopleRequest{UserId: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem people with invalid mediaitem user id", &api.MediaItemPeopleRequest{UserId: "bad-mediaitem-id"}, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem people with invalid mediaitem id", - &api.MediaItemPeopleRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemFacePeople: map[string]*api.MediaItemFacePeople{"bad-mediaitem-id": nil}}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem people with invalid mediaitem id", &api.MediaItemPeopleRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemFacePeople: map[string]*api.MediaItemFacePeople{ + "bad-mediaitem-id": nil, + }, + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { - "save mediaitem people with invalid face id", - &api.MediaItemPeopleRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemFacePeople: map[string]*api.MediaItemFacePeople{"4d05b5f6-17c2-475e-87fe-3fc8b9567179": { - FacePeople: map[string]string{"bad-face-id": "bad-people-id"}, - }}}, - nil, - status.Errorf(codes.InvalidArgument, "invalid face id"), + "save mediaitem people with invalid face id", &api.MediaItemPeopleRequest{ + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", MediaItemFacePeople: map[string]*api.MediaItemFacePeople{ + "4d05b5f6-17c2-475e-87fe-3fc8b9567179": { + FacePeople: map[string]string{ + "bad-face-id": "bad-people-id", + }, + }, + }, + }, nil, status.Errorf(codes.InvalidArgument, "invalid face id"), }, { - "save mediaitem people with success", - &mediaItemPeopleRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitem_faces"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitem_faces"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + "save mediaitem people with error starting transaction", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}).WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error starting transaction for saving mediaitem people: some db error"), }, { - "save mediaitem people with error saving people", - &mediaItemPeopleRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people"`)). + "save mediaitem people with error saving people", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving people: some db error"), + }, status.Error(codes.Internal, "error saving people: some db error"), }, { - "save mediaitem people with error saving people mediaitems", - &mediaItemPeopleRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people_mediaitems"`)). + "save mediaitem people with error saving people mediaitems", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving people mediaitems: some db error"), + }, status.Error(codes.Internal, "error saving people mediaitems: some db error"), + }, + { + "save mediaitem people with error saving mediaitem faces", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error saving mediaitem faces: some db error"), + }, + { + "save mediaitem people with error committing transaction", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectCommit().WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error committing transaction for saving mediaitem people: some db error"), }, { - "save mediaitem people with error saving mediaitem faces people", - &mediaItemPeopleRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "people"`)). - WillReturnRows(sqlmock.NewRows(peopleCols)) - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) + "save mediaitem people with success", &mediaItemPeopleRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "people"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "people_mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectBeginTx(pgx.TxOptions{}) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO people_mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitem_faces`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitem_faces"`)). - WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem faces people: some db error"), + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -1142,86 +902,82 @@ func TestSaveMediaItemFinalResult(t *testing.T) { tests := []struct { Name string Request *api.MediaItemFinalResultRequest - MockDB func(mock sqlmock.Sqlmock) + MockDB func(mock pgxmock.PgxPoolIface) ExpectedErr error }{ { - "save mediaitem ml result with invalid mediaitem user id", - &api.MediaItemFinalResultRequest{UserId: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), + "save mediaitem ml result with invalid queue id", &api.MediaItemFinalResultRequest{Id: "bad-queue-id"}, nil, status.Errorf(codes.InvalidArgument, "invalid queue id"), }, { - "save mediaitem ml result with invalid mediaitem id", - &api.MediaItemFinalResultRequest{UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", Id: "bad-mediaitem-id"}, - nil, - status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), + "save mediaitem ml result with invalid user id", &api.MediaItemFinalResultRequest{ + Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + UserId: "bad-mediaitem-user-id", + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem user id"), }, { - "save mediaitem final result with success", - &mediaItemFinalResultRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitem_embeddings"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - }, - nil, + "save mediaitem ml result with invalid mediaitem id", &api.MediaItemFinalResultRequest{ + Id: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + UserId: "4d05b5f6-17c2-475e-87fe-3fc8b9567179", + MediaItemId: "bad-mediaitem-id", + }, nil, status.Errorf(codes.InvalidArgument, "invalid mediaitem id"), }, { - "save mediaitem final result with error saving keywords", - &mediaItemFinalResultRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). + "save mediaitem final result with error saving final result", &mediaItemFinalResultRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem final result: some db error"), + }, status.Error(codes.Internal, "error saving mediaitem final result: some db error"), }, { - "save mediaitem final result with error saving embeddings", - &mediaItemFinalResultRequest, - func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "mediaitems"`)). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - mock.ExpectBegin() - mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "mediaitem_embeddings"`)). + "save mediaitem final result with error saving embeddings", &mediaItemFinalResultRequest, func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitem_embeddings`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). WillReturnError(errors.New("some db error")) - mock.ExpectRollback() - }, - status.Error(codes.Internal, "error saving mediaitem final result: some db error"), + }, status.Error(codes.Internal, "error saving mediaitem final result embedding: some db error"), + }, + { + "save mediaitem final result with error unqueuing mediaitem from processing", &mediaItemFinalResultRequest, + func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitem_embeddings`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM queue`)). + WithArgs(pgxmock.AnyArg()). + WillReturnError(errors.New("some db error")) + }, status.Error(codes.Internal, "error unqueuing mediaitem from processing: some db error"), + }, + { + "save mediaitem final result with success", &mediaItemFinalResultRequest, + func(mock pgxmock.PgxPoolIface) { + mock.ExpectExec(regexp.QuoteMeta(`UPDATE mediaitems`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("UPDATE", 1)) + mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO mediaitem_embeddings`)). + WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("INSERT", 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM queue`)). + WithArgs(pgxmock.AnyArg()). + WillReturnResult(pgxmock.NewResult("DELETE", 1)) + }, nil, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // database - mockDB, mock, err := sqlmock.New() - assert.NoError(t, err) + mockDB, err := pgxmock.NewPool() + require.NoError(t, err) defer mockDB.Close() - mockGDB, err := gorm.Open(postgres.New(postgres.Config{ - DSN: "sqlmock", - DriverName: "postgres", - Conn: mockDB, - PreferSimpleProtocol: true, - }), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Error), - }) - assert.NoError(t, err) if test.MockDB != nil { - test.MockDB(mock) + test.MockDB(mockDB) } // service - service := &Service{ - Config: &config.Config{}, - DB: mockGDB, - } + service := Init(&config.Config{}, mockDB, nil) // server ctx := context.Background() conn, err := grpc.DialContext(ctx, "", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -1250,37 +1006,31 @@ func dialer(service *Service) func(context.Context, string) (net.Conn, error) { } } -func getMockedPlaceRow() *sqlmock.Rows { - return sqlmock.NewRows(placeCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", "postcode", "town", "city", - "state", "country", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime) -} - -func getMockedThingRow() *sqlmock.Rows { - return sqlmock.NewRows(thingCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "name", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", "true", sampleTime, sampleTime) +func getMockedMediaItemFaceEmbeddingRows(bad bool) *pgxmock.Rows { + if bad { + return pgxmock.NewRows(mediaitemFaceCols). + AddRow("invalid", "invalid", nil, nil) + } + return pgxmock.NewRows(mediaitemFaceCols). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleId, &sampleEmbedding). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", &sampleId, &sampleEmbedding) } -func getMockedMediaItemRow(existingKeyword *string) *sqlmock.Rows { - return sqlmock.NewRows(mediaitemCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "filename", "description", "mime_type", existingKeyword, "source_url", "preview_url", - "thumbnail_url", "true", "false", "false", "status", "mediaitem_type", "mediaitem_category", 720, - 480, sampleTime, "camera_make", "camera_model", "focal_length", "aperture_fnumber", - "iso_equivalent", "exposure_time", "17.580249", "-70.278493", "fps", sampleTime, sampleTime) -} +func getMockedMediaItemToProcessRow() *pgxmock.Rows { + return pgxmock.NewRows([]string{"id", "user_id", "mediaitem_id", "components", "mime_type", "source_url", + "preview_url", "mediaitem_type", "mediaitem_category", "latitude", "longitude"}). + AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567180", + "4d05b5f6-17c2-475e-87fe-3fc8b9567181", components, &mimetype, sourceUrl, + &previewUrl, &mediaitemType, &mediaitemCategory, &latitude, &longitude) -func getMockedMediaItemFaceEmbeddingRows() *sqlmock.Rows { - return sqlmock.NewRows(mediaitemFaceCols). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - nil, embedding). - AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179", "4d05b5f6-17c2-475e-87fe-3fc8b9567179", - "4d05b5f6-17c2-475e-87fe-3fc8b9567179", embedding) } -func getMockedUserIDRows() *sqlmock.Rows { - return sqlmock.NewRows([]string{"id"}). +func getMockedUserIDRows(bad bool) *pgxmock.Rows { + if bad { + return pgxmock.NewRows([]string{"id"}). + AddRow("invalid") + } + return pgxmock.NewRows([]string{"id"}). AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567179"). AddRow("4d05b5f6-17c2-475e-87fe-3fc8b9567180") } diff --git a/api/main.go b/api/main.go index e5e403e7..db69ddbf 100644 --- a/api/main.go +++ b/api/main.go @@ -3,8 +3,6 @@ package main import ( "api/config" "api/internal/handlers" - "api/internal/jobs" - "api/internal/models" "api/internal/server" "api/internal/service" "api/pkg/cache" @@ -21,49 +19,30 @@ import ( "google.golang.org/grpc/credentials/insecure" ) -//nolint:funlen func main() { cfg, err := config.Init() if err != nil { panic(err) } - pgDB, err := database.Init(cfg.Database.LogLevel, cfg.Database.Host, cfg.Database.Port, - cfg.Database.Username, cfg.Database.Password, cfg.Database.Name) - if err != nil { - panic(err) - } - err = pgDB.AutoMigrate(models.GetModels()...) + pgDB, err := database.Init(cfg.Database.Host, cfg.Database.Port, cfg.Database.Username, + cfg.Database.Password, cfg.Name, cfg.Timeout) if err != nil { panic(err) } - cache := cache.Init(cfg) + cache := cache.Init(cfg.Type, cfg.Cache.Host, cfg.Cache.Port, cfg.Cache.Password) storageProvider := storage.Init(&storage.Config{ - Provider: cfg.Storage.Provider, Root: cfg.Storage.DiskRoot, - Endpoint: cfg.Storage.Endpoint, AccessKey: cfg.Storage.AccessKey, SecretKey: cfg.Storage.SecretKey, + Provider: cfg.Provider, Root: cfg.DiskRoot, Endpoint: cfg.Endpoint, + AccessKey: cfg.AccessKey, SecretKey: cfg.SecretKey, }) - service := &service.Service{ - Config: cfg, - DB: pgDB, - Storage: storageProvider, - } + service := service.Init(cfg, pgDB, storageProvider) grpcServer := server.StartGRPCServer(cfg, service) - err = pgDB.Callback().Query().Register("mediaItemUrl", (&models.MediaItemURLPlugin{ - Storage: storageProvider, - Cache: cache, - }).TransformMediaItemURL) - if err != nil { - panic(err) - } - handler := &handlers.Handler{ - Config: cfg, - DB: pgDB, - Cache: cache, + Config: cfg, DB: pgDB, Cache: cache, } httpServer := server.StartHTTPServer(handler) @@ -76,14 +55,6 @@ func main() { } handler.Worker = worker.NewWorkerClient(conn) - jobsInstance := &jobs.Job{ - Config: cfg, - DB: pgDB, - Storage: storageProvider, - Worker: worker.NewWorkerClient(conn), - } - go jobsInstance.StartJobs() - // graceful shutdown shutdownSignal := make(chan os.Signal, 1) signal.Notify(shutdownSignal, syscall.SIGINT, syscall.SIGTERM) diff --git a/api/pkg/cache/cache.go b/api/pkg/cache/cache.go index e0913834..d6d180aa 100644 --- a/api/pkg/cache/cache.go +++ b/api/pkg/cache/cache.go @@ -1,7 +1,6 @@ package cache import ( - "api/config" "fmt" "math" "time" @@ -18,20 +17,20 @@ type Provider interface { } // Init ... -func Init(config *config.Config) Provider { //nolint: ireturn - switch config.Cache.Type { +func Init(cacheType, host string, port int, password string) Provider { //nolint: ireturn + switch cacheType { case "redis": + addr := fmt.Sprintf("%s:%d", host, port) + return &RedisCache{ - Connection: &redisClient{client: redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:%d", config.Cache.Host, config.Cache.Port), - Password: config.Cache.Password, - })}, + Connection: &redisClient{ + client: redis.NewClient(&redis.Options{ + Addr: addr, + Password: password, + }), + }, } default: - return &InMemoryCache{ - Connection: gcache.New(math.MaxInt). - LRU(). - Build(), - } + return &InMemoryCache{Connection: gcache.New(math.MaxInt).LRU().Build()} } } diff --git a/api/pkg/cache/cache_test.go b/api/pkg/cache/cache_test.go index 87d5523b..4388569f 100644 --- a/api/pkg/cache/cache_test.go +++ b/api/pkg/cache/cache_test.go @@ -4,14 +4,12 @@ import ( "reflect" "testing" - "api/config" - "github.com/stretchr/testify/assert" ) func TestInit(t *testing.T) { - cache := Init(&config.Config{Cache: config.Cache{Type: "inmemory"}}) + cache := Init("inmemory", "", 0, "") assert.Equal(t, reflect.TypeOf(&InMemoryCache{}), reflect.TypeOf(cache)) - cache = Init(&config.Config{Cache: config.Cache{Type: "redis"}}) + cache = Init("redis", "username", 0, "password") assert.Equal(t, reflect.TypeOf(&RedisCache{}), reflect.TypeOf(cache)) } diff --git a/api/pkg/cache/inmemory.go b/api/pkg/cache/inmemory.go index da7c7ac3..011dbf86 100644 --- a/api/pkg/cache/inmemory.go +++ b/api/pkg/cache/inmemory.go @@ -1,6 +1,7 @@ package cache import ( + "errors" "fmt" "time" @@ -12,6 +13,8 @@ type InMemoryCache struct { Connection gcache.Cache } +var errRemovingFromCache = errors.New("error removing from cache") + func (imc *InMemoryCache) SetWithExpire(key string, value interface{}, expiration time.Duration) error { return imc.Connection.SetWithExpire(key, value, expiration) } @@ -23,7 +26,8 @@ func (imc *InMemoryCache) Get(key string) (interface{}, error) { func (imc *InMemoryCache) Remove(key string) error { result := imc.Connection.Remove(key) if !result { - return fmt.Errorf("error removing from cache for key: %+v", key) + return fmt.Errorf("%w for key: %+v", errRemovingFromCache, key) } + return nil } diff --git a/api/pkg/cache/redis.go b/api/pkg/cache/redis.go index d653891d..6aaffd4c 100644 --- a/api/pkg/cache/redis.go +++ b/api/pkg/cache/redis.go @@ -7,8 +7,7 @@ import ( "github.com/go-redis/redis/v8" ) -type ( - // RedisClient ... +type ( // RedisClient ... RedisClient interface { Get(ctx context.Context, key string) (string, error) Set(ctx context.Context, key string, val interface{}, ttl time.Duration) error diff --git a/api/pkg/database/database.go b/api/pkg/database/database.go index e65650f0..54dec4e7 100644 --- a/api/pkg/database/database.go +++ b/api/pkg/database/database.go @@ -1,37 +1,45 @@ package database import ( + "context" "fmt" + "net" + "strconv" + "time" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) +type DBInterface interface { + Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, args ...any) pgx.Row + Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) + Begin(ctx context.Context) (pgx.Tx, error) + Close() +} + // Init ... -func Init(logLevel, host string, port int, username, password, name string) (*gorm.DB, error) { - dsn := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", username, password, host, port, name) - dbConn, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: logger.Default.LogMode(getLogLevel(logLevel)), - }) +// +//nolint:ireturn +func Init(host string, port int, username, password, name string, timeout time.Duration) (DBInterface, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + dsn := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", username, password, net.JoinHostPort(host, strconv.Itoa(port)), name) + + conn, err := pgxpool.New(ctx, dsn) if err != nil { - return nil, err - } - result := dbConn.Exec("CREATE EXTENSION IF NOT EXISTS vector") - if result.Error != nil { - return nil, result.Error + return nil, err //nolint: wrapcheck } - return dbConn, nil -} -func getLogLevel(logLevel string) logger.LogLevel { - switch logLevel { - case "ERROR": - return logger.Error - case "WARN": - return logger.Warn - case "INFO": - return logger.Info + err = conn.Ping(ctx) + if err != nil { + conn.Close() + + return nil, err //nolint: wrapcheck } - return logger.Silent + + return conn, nil } diff --git a/api/pkg/database/database_test.go b/api/pkg/database/database_test.go index b3fa88a9..52a04cc8 100644 --- a/api/pkg/database/database_test.go +++ b/api/pkg/database/database_test.go @@ -2,20 +2,13 @@ package database import ( "testing" + "time" "github.com/stretchr/testify/assert" - "gorm.io/gorm/logger" ) -func TestGetLogLevel(t *testing.T) { - assert.Equal(t, logger.Error, getLogLevel("ERROR")) - assert.Equal(t, logger.Warn, getLogLevel("WARN")) - assert.Equal(t, logger.Info, getLogLevel("INFO")) - assert.Equal(t, logger.Silent, getLogLevel("")) -} - func TestInit(t *testing.T) { - db, err := Init("WARNING", "host", 1000, "username", "password", "name") + db, err := Init("host", 1000, "username", "password", "name", time.Second) assert.Nil(t, db) assert.Error(t, err) } diff --git a/api/pkg/database/migrations/000001_init.down.sql b/api/pkg/database/migrations/000001_init.down.sql new file mode 100644 index 00000000..153d3859 --- /dev/null +++ b/api/pkg/database/migrations/000001_init.down.sql @@ -0,0 +1,13 @@ +DROP EXTENSION IF EXISTS vector CASCADE; +DROP TABLE IF EXISTS queue CASCADE; +DROP TABLE IF EXISTS mediaitem_embeddings CASCADE; +DROP TABLE IF EXISTS mediaitem_faces CASCADE; +DROP TABLE IF EXISTS album_mediaitems CASCADE; +DROP TABLE IF EXISTS people_mediaitems CASCADE; +DROP TABLE IF EXISTS place_mediaitems CASCADE; +DROP TABLE IF EXISTS mediaitems CASCADE; +DROP TABLE IF EXISTS albums CASCADE; +DROP TABLE IF EXISTS places CASCADE; +DROP TABLE IF EXISTS people CASCADE; +DROP TABLE IF EXISTS jobs CASCADE; +DROP TABLE IF EXISTS users CASCADE; \ No newline at end of file diff --git a/api/pkg/database/migrations/000001_init.up.sql b/api/pkg/database/migrations/000001_init.up.sql new file mode 100644 index 00000000..7bd4e687 --- /dev/null +++ b/api/pkg/database/migrations/000001_init.up.sql @@ -0,0 +1,240 @@ +CREATE EXTENSION IF NOT EXISTS vector; + +CREATE TABLE album_mediaitems ( + mediaitem_id uuid NOT NULL, + album_id uuid NOT NULL +); + +CREATE TABLE albums ( + id uuid NOT NULL, + user_id uuid NOT NULL, + name text, + description text, + is_shared boolean DEFAULT false, + is_hidden boolean DEFAULT false, + mediaitems_count bigint DEFAULT 0, + cover_mediaitem_id uuid, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE jobs ( + id uuid NOT NULL, + user_id uuid NOT NULL, + status text, + components text, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE queue ( + id uuid NOT NULL, + user_id uuid NOT NULL, + mediaitem_id uuid NOT NULL, + components text, + status text +); + +CREATE TABLE mediaitem_embeddings ( + mediaitem_id uuid, + embedding vector +); + +CREATE TABLE mediaitem_faces ( + id uuid NOT NULL, + mediaitem_id uuid, + people_id uuid, + embedding vector, + thumbnail text +); + +CREATE TABLE mediaitems ( + id uuid NOT NULL, + user_id uuid NOT NULL, + filename text, + hash text, + description text, + mime_type text, + source_url text, + preview_url text, + thumbnail_url text, + placeholder text, + is_favourite boolean DEFAULT false, + is_hidden boolean DEFAULT false, + is_deleted boolean DEFAULT false, + status text, + mediaitem_type text, + mediaitem_category text, + width bigint, + height bigint, + creation_time timestamp with time zone, + camera_make text, + camera_model text, + focal_length text, + aperture_fnumber text, + iso_equivalent text, + exposure_time text, + megapixels text, + latitude numeric, + longitude numeric, + fps text, + exif_data text, + detected_text text, + caption text, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE people ( + id uuid NOT NULL, + user_id uuid NOT NULL, + name text, + is_hidden boolean DEFAULT false, + cover_mediaitem_id uuid, + cover_mediaitem_face_id uuid, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE people_mediaitems ( + mediaitem_id uuid NOT NULL, + people_id uuid NOT NULL +); + +CREATE TABLE place_mediaitems ( + mediaitem_id uuid NOT NULL, + place_id uuid NOT NULL +); + +CREATE TABLE places ( + id uuid NOT NULL, + user_id uuid NOT NULL, + name text, + postcode text, + country text, + locality text, + area text, + is_hidden boolean DEFAULT false, + cover_mediaitem_id uuid, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +CREATE TABLE users ( + id uuid NOT NULL, + name text, + username text, + password text, + features text, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + +ALTER TABLE ONLY album_mediaitems +ADD CONSTRAINT album_mediaitems_pkey PRIMARY KEY (mediaitem_id, album_id); + +ALTER TABLE ONLY albums +ADD CONSTRAINT albums_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY jobs +ADD CONSTRAINT jobs_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY mediaitem_faces +ADD CONSTRAINT mediaitem_faces_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY mediaitems +ADD CONSTRAINT mediaitems_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY people_mediaitems +ADD CONSTRAINT people_mediaitems_pkey PRIMARY KEY (mediaitem_id, people_id); + +ALTER TABLE ONLY people +ADD CONSTRAINT people_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY place_mediaitems +ADD CONSTRAINT place_mediaitems_pkey PRIMARY KEY (mediaitem_id, place_id); + +ALTER TABLE ONLY places +ADD CONSTRAINT places_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY queue +ADD CONSTRAINT queue_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY users +ADD CONSTRAINT uni_users_username UNIQUE (username); + +ALTER TABLE ONLY users +ADD CONSTRAINT users_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX idx_albums_id ON albums USING btree (id); + +CREATE UNIQUE INDEX idx_jobs_id ON jobs USING btree (id); + +CREATE UNIQUE INDEX idx_mediaitem_faces_id ON mediaitem_faces USING btree (id); + +CREATE UNIQUE INDEX idx_mediaitem_faces_mediaitem_id_people_id ON mediaitem_faces USING btree (mediaitem_id, people_id); + +CREATE UNIQUE INDEX idx_mediaitems_id ON mediaitems USING btree (id); + +CREATE UNIQUE INDEX idx_mediaitems_user_id_hash ON mediaitems USING btree (user_id, hash); + +CREATE UNIQUE INDEX idx_people_id ON people USING btree (id); + +CREATE UNIQUE INDEX idx_places_id ON places USING btree (id); + +CREATE UNIQUE INDEX idx_places_user_id_name_postcode ON places USING btree (user_id, name, postcode); + +CREATE UNIQUE INDEX idx_queue_id ON queue USING btree (id); + +CREATE INDEX idx_queue_unspecified_id ON queue (id) WHERE status = 'UNSPECIFIED'; + +ALTER TABLE ONLY album_mediaitems +ADD CONSTRAINT fk_album_mediaitems_album FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE; + +ALTER TABLE ONLY album_mediaitems +ADD CONSTRAINT fk_album_mediaitems_media_item FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; + +ALTER TABLE ONLY albums +ADD CONSTRAINT fk_albums_cover_media_item FOREIGN KEY (cover_mediaitem_id) REFERENCES mediaitems(id) ON DELETE SET NULL; + +ALTER TABLE ONLY albums +ADD CONSTRAINT fk_albums_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY mediaitem_embeddings +ADD CONSTRAINT fk_mediaitems_embeddings FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; + +ALTER TABLE ONLY mediaitem_faces +ADD CONSTRAINT fk_mediaitems_faces FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; + +ALTER TABLE ONLY people +ADD CONSTRAINT fk_people_cover_media_item FOREIGN KEY (cover_mediaitem_id) REFERENCES mediaitems(id) ON DELETE SET NULL; + +ALTER TABLE ONLY people +ADD CONSTRAINT fk_people_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY people +ADD CONSTRAINT fk_people_cover_media_item_face FOREIGN KEY (cover_mediaitem_face_id) REFERENCES mediaitem_faces(id); + +ALTER TABLE ONLY people_mediaitems +ADD CONSTRAINT fk_people_mediaitems_media_item FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; + +ALTER TABLE ONLY people_mediaitems +ADD CONSTRAINT fk_people_mediaitems_people FOREIGN KEY (people_id) REFERENCES people(id) ON DELETE CASCADE; + +ALTER TABLE ONLY place_mediaitems +ADD CONSTRAINT fk_place_mediaitems_media_item FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; + +ALTER TABLE ONLY place_mediaitems +ADD CONSTRAINT fk_place_mediaitems_place FOREIGN KEY (place_id) REFERENCES places(id) ON DELETE CASCADE; + +ALTER TABLE ONLY places +ADD CONSTRAINT fk_places_cover_media_item FOREIGN KEY (cover_mediaitem_id) REFERENCES mediaitems(id) ON DELETE SET NULL; + +ALTER TABLE ONLY places +ADD CONSTRAINT fk_places_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY queue +ADD CONSTRAINT fk_queue_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + +ALTER TABLE ONLY queue +ADD CONSTRAINT fk_queue_media_item FOREIGN KEY (mediaitem_id) REFERENCES mediaitems(id) ON DELETE CASCADE; diff --git a/api/pkg/services/api/api.pb.go b/api/pkg/services/api/api.pb.go index 2ca09ec0..8b823497 100644 --- a/api/pkg/services/api/api.pb.go +++ b/api/pkg/services/api/api.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.9 // protoc v5.29.3 // source: api.proto @@ -22,6 +22,229 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type MediaItemComponent int32 + +const ( + MediaItemComponent_METADATA MediaItemComponent = 0 + MediaItemComponent_PREVIEW_THUMBNAIL MediaItemComponent = 1 + MediaItemComponent_PLACES MediaItemComponent = 2 + MediaItemComponent_CLASSIFICATION MediaItemComponent = 3 + MediaItemComponent_FACES MediaItemComponent = 4 + MediaItemComponent_OCR MediaItemComponent = 5 + MediaItemComponent_SEARCH MediaItemComponent = 6 +) + +// Enum value maps for MediaItemComponent. +var ( + MediaItemComponent_name = map[int32]string{ + 0: "METADATA", + 1: "PREVIEW_THUMBNAIL", + 2: "PLACES", + 3: "CLASSIFICATION", + 4: "FACES", + 5: "OCR", + 6: "SEARCH", + } + MediaItemComponent_value = map[string]int32{ + "METADATA": 0, + "PREVIEW_THUMBNAIL": 1, + "PLACES": 2, + "CLASSIFICATION": 3, + "FACES": 4, + "OCR": 5, + "SEARCH": 6, + } +) + +func (x MediaItemComponent) Enum() *MediaItemComponent { + p := new(MediaItemComponent) + *p = x + return p +} + +func (x MediaItemComponent) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MediaItemComponent) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[0].Descriptor() +} + +func (MediaItemComponent) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[0] +} + +func (x MediaItemComponent) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MediaItemComponent.Descriptor instead. +func (MediaItemComponent) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +type MediaItemStatus int32 + +const ( + MediaItemStatus_UNSPECIFIED MediaItemStatus = 0 + MediaItemStatus_PROCESSING MediaItemStatus = 1 + MediaItemStatus_READY MediaItemStatus = 2 + MediaItemStatus_FAILED MediaItemStatus = 3 +) + +// Enum value maps for MediaItemStatus. +var ( + MediaItemStatus_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "PROCESSING", + 2: "READY", + 3: "FAILED", + } + MediaItemStatus_value = map[string]int32{ + "UNSPECIFIED": 0, + "PROCESSING": 1, + "READY": 2, + "FAILED": 3, + } +) + +func (x MediaItemStatus) Enum() *MediaItemStatus { + p := new(MediaItemStatus) + *p = x + return p +} + +func (x MediaItemStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MediaItemStatus) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[1].Descriptor() +} + +func (MediaItemStatus) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[1] +} + +func (x MediaItemStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MediaItemStatus.Descriptor instead. +func (MediaItemStatus) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +type MediaItemType int32 + +const ( + MediaItemType_UNKNOWN MediaItemType = 0 + MediaItemType_PHOTO MediaItemType = 1 + MediaItemType_VIDEO MediaItemType = 2 +) + +// Enum value maps for MediaItemType. +var ( + MediaItemType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "PHOTO", + 2: "VIDEO", + } + MediaItemType_value = map[string]int32{ + "UNKNOWN": 0, + "PHOTO": 1, + "VIDEO": 2, + } +) + +func (x MediaItemType) Enum() *MediaItemType { + p := new(MediaItemType) + *p = x + return p +} + +func (x MediaItemType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MediaItemType) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[2].Descriptor() +} + +func (MediaItemType) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[2] +} + +func (x MediaItemType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MediaItemType.Descriptor instead. +func (MediaItemType) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +type MediaItemCategory int32 + +const ( + MediaItemCategory_DEFAULT MediaItemCategory = 0 + MediaItemCategory_SCREENSHOT MediaItemCategory = 1 + MediaItemCategory_PANORAMA MediaItemCategory = 2 + MediaItemCategory_SLOW MediaItemCategory = 3 + MediaItemCategory_MOTION MediaItemCategory = 4 + MediaItemCategory_LIVE MediaItemCategory = 5 + MediaItemCategory_TIMELAPSE MediaItemCategory = 6 +) + +// Enum value maps for MediaItemCategory. +var ( + MediaItemCategory_name = map[int32]string{ + 0: "DEFAULT", + 1: "SCREENSHOT", + 2: "PANORAMA", + 3: "SLOW", + 4: "MOTION", + 5: "LIVE", + 6: "TIMELAPSE", + } + MediaItemCategory_value = map[string]int32{ + "DEFAULT": 0, + "SCREENSHOT": 1, + "PANORAMA": 2, + "SLOW": 3, + "MOTION": 4, + "LIVE": 5, + "TIMELAPSE": 6, + } +) + +func (x MediaItemCategory) Enum() *MediaItemCategory { + p := new(MediaItemCategory) + *p = x + return p +} + +func (x MediaItemCategory) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MediaItemCategory) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[3].Descriptor() +} + +func (MediaItemCategory) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[3] +} + +func (x MediaItemCategory) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use MediaItemCategory.Descriptor instead. +func (MediaItemCategory) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + type ConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Config []byte `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` @@ -66,14 +289,90 @@ func (x *ConfigResponse) GetConfig() []byte { return nil } +type MediaItemProcessResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=userId,proto3" json:"userId,omitempty"` + MediaItemId string `protobuf:"bytes,3,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` + Components []MediaItemComponent `protobuf:"varint,4,rep,packed,name=components,proto3,enum=MediaItemComponent" json:"components,omitempty"` + Payload map[string]string `protobuf:"bytes,5,rep,name=payload,proto3" json:"payload,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MediaItemProcessResponse) Reset() { + *x = MediaItemProcessResponse{} + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MediaItemProcessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MediaItemProcessResponse) ProtoMessage() {} + +func (x *MediaItemProcessResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MediaItemProcessResponse.ProtoReflect.Descriptor instead. +func (*MediaItemProcessResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (x *MediaItemProcessResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *MediaItemProcessResponse) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *MediaItemProcessResponse) GetMediaItemId() string { + if x != nil { + return x.MediaItemId + } + return "" +} + +func (x *MediaItemProcessResponse) GetComponents() []MediaItemComponent { + if x != nil { + return x.Components + } + return nil +} + +func (x *MediaItemProcessResponse) GetPayload() map[string]string { + if x != nil { + return x.Payload + } + return nil +} + type MediaItemMetadataRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + MediaItemId string `protobuf:"bytes,2,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` + Status MediaItemStatus `protobuf:"varint,3,opt,name=status,proto3,enum=MediaItemStatus" json:"status,omitempty"` MimeType *string `protobuf:"bytes,4,opt,name=mimeType,proto3,oneof" json:"mimeType,omitempty"` - Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"` - Category string `protobuf:"bytes,6,opt,name=category,proto3" json:"category,omitempty"` + Type MediaItemType `protobuf:"varint,5,opt,name=type,proto3,enum=MediaItemType" json:"type,omitempty"` + Category MediaItemCategory `protobuf:"varint,6,opt,name=category,proto3,enum=MediaItemCategory" json:"category,omitempty"` Width *int32 `protobuf:"varint,7,opt,name=width,proto3,oneof" json:"width,omitempty"` Height *int32 `protobuf:"varint,8,opt,name=height,proto3,oneof" json:"height,omitempty"` CreationTime *string `protobuf:"bytes,9,opt,name=creationTime,proto3,oneof" json:"creationTime,omitempty"` @@ -83,17 +382,18 @@ type MediaItemMetadataRequest struct { ApertureFNumber *string `protobuf:"bytes,13,opt,name=apertureFNumber,proto3,oneof" json:"apertureFNumber,omitempty"` IsoEquivalent *string `protobuf:"bytes,14,opt,name=isoEquivalent,proto3,oneof" json:"isoEquivalent,omitempty"` ExposureTime *string `protobuf:"bytes,15,opt,name=exposureTime,proto3,oneof" json:"exposureTime,omitempty"` - Fps *string `protobuf:"bytes,16,opt,name=fps,proto3,oneof" json:"fps,omitempty"` - Latitude *float64 `protobuf:"fixed64,17,opt,name=latitude,proto3,oneof" json:"latitude,omitempty"` - Longitude *float64 `protobuf:"fixed64,18,opt,name=longitude,proto3,oneof" json:"longitude,omitempty"` - ExifData *string `protobuf:"bytes,19,opt,name=exifData,proto3,oneof" json:"exifData,omitempty"` + Megapixels *string `protobuf:"bytes,16,opt,name=megapixels,proto3,oneof" json:"megapixels,omitempty"` + Fps *string `protobuf:"bytes,17,opt,name=fps,proto3,oneof" json:"fps,omitempty"` + Latitude *float64 `protobuf:"fixed64,18,opt,name=latitude,proto3,oneof" json:"latitude,omitempty"` + Longitude *float64 `protobuf:"fixed64,19,opt,name=longitude,proto3,oneof" json:"longitude,omitempty"` + ExifData *string `protobuf:"bytes,20,opt,name=exifData,proto3,oneof" json:"exifData,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MediaItemMetadataRequest) Reset() { *x = MediaItemMetadataRequest{} - mi := &file_api_proto_msgTypes[1] + mi := &file_api_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -105,7 +405,7 @@ func (x *MediaItemMetadataRequest) String() string { func (*MediaItemMetadataRequest) ProtoMessage() {} func (x *MediaItemMetadataRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[1] + mi := &file_api_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -118,7 +418,7 @@ func (x *MediaItemMetadataRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MediaItemMetadataRequest.ProtoReflect.Descriptor instead. func (*MediaItemMetadataRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{1} + return file_api_proto_rawDescGZIP(), []int{2} } func (x *MediaItemMetadataRequest) GetUserId() string { @@ -128,18 +428,18 @@ func (x *MediaItemMetadataRequest) GetUserId() string { return "" } -func (x *MediaItemMetadataRequest) GetId() string { +func (x *MediaItemMetadataRequest) GetMediaItemId() string { if x != nil { - return x.Id + return x.MediaItemId } return "" } -func (x *MediaItemMetadataRequest) GetStatus() string { +func (x *MediaItemMetadataRequest) GetStatus() MediaItemStatus { if x != nil { return x.Status } - return "" + return MediaItemStatus_UNSPECIFIED } func (x *MediaItemMetadataRequest) GetMimeType() string { @@ -149,18 +449,18 @@ func (x *MediaItemMetadataRequest) GetMimeType() string { return "" } -func (x *MediaItemMetadataRequest) GetType() string { +func (x *MediaItemMetadataRequest) GetType() MediaItemType { if x != nil { return x.Type } - return "" + return MediaItemType_UNKNOWN } -func (x *MediaItemMetadataRequest) GetCategory() string { +func (x *MediaItemMetadataRequest) GetCategory() MediaItemCategory { if x != nil { return x.Category } - return "" + return MediaItemCategory_DEFAULT } func (x *MediaItemMetadataRequest) GetWidth() int32 { @@ -226,6 +526,13 @@ func (x *MediaItemMetadataRequest) GetExposureTime() string { return "" } +func (x *MediaItemMetadataRequest) GetMegapixels() string { + if x != nil && x.Megapixels != nil { + return *x.Megapixels + } + return "" +} + func (x *MediaItemMetadataRequest) GetFps() string { if x != nil && x.Fps != nil { return *x.Fps @@ -257,8 +564,8 @@ func (x *MediaItemMetadataRequest) GetExifData() string { type MediaItemPreviewThumbnailRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + MediaItemId string `protobuf:"bytes,2,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` + Status MediaItemStatus `protobuf:"varint,3,opt,name=status,proto3,enum=MediaItemStatus" json:"status,omitempty"` SourcePath *string `protobuf:"bytes,4,opt,name=sourcePath,proto3,oneof" json:"sourcePath,omitempty"` PreviewPath *string `protobuf:"bytes,5,opt,name=previewPath,proto3,oneof" json:"previewPath,omitempty"` ThumbnailPath *string `protobuf:"bytes,6,opt,name=thumbnailPath,proto3,oneof" json:"thumbnailPath,omitempty"` @@ -269,7 +576,7 @@ type MediaItemPreviewThumbnailRequest struct { func (x *MediaItemPreviewThumbnailRequest) Reset() { *x = MediaItemPreviewThumbnailRequest{} - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -281,7 +588,7 @@ func (x *MediaItemPreviewThumbnailRequest) String() string { func (*MediaItemPreviewThumbnailRequest) ProtoMessage() {} func (x *MediaItemPreviewThumbnailRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -294,7 +601,7 @@ func (x *MediaItemPreviewThumbnailRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MediaItemPreviewThumbnailRequest.ProtoReflect.Descriptor instead. func (*MediaItemPreviewThumbnailRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} + return file_api_proto_rawDescGZIP(), []int{3} } func (x *MediaItemPreviewThumbnailRequest) GetUserId() string { @@ -304,18 +611,18 @@ func (x *MediaItemPreviewThumbnailRequest) GetUserId() string { return "" } -func (x *MediaItemPreviewThumbnailRequest) GetId() string { +func (x *MediaItemPreviewThumbnailRequest) GetMediaItemId() string { if x != nil { - return x.Id + return x.MediaItemId } return "" } -func (x *MediaItemPreviewThumbnailRequest) GetStatus() string { +func (x *MediaItemPreviewThumbnailRequest) GetStatus() MediaItemStatus { if x != nil { return x.Status } - return "" + return MediaItemStatus_UNSPECIFIED } func (x *MediaItemPreviewThumbnailRequest) GetSourcePath() string { @@ -349,19 +656,18 @@ func (x *MediaItemPreviewThumbnailRequest) GetPlaceholder() string { type MediaItemPlaceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + MediaItemId string `protobuf:"bytes,2,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` Postcode *string `protobuf:"bytes,3,opt,name=postcode,proto3,oneof" json:"postcode,omitempty"` Country *string `protobuf:"bytes,4,opt,name=country,proto3,oneof" json:"country,omitempty"` - State *string `protobuf:"bytes,5,opt,name=state,proto3,oneof" json:"state,omitempty"` - City *string `protobuf:"bytes,6,opt,name=city,proto3,oneof" json:"city,omitempty"` - Town *string `protobuf:"bytes,7,opt,name=town,proto3,oneof" json:"town,omitempty"` + Locality *string `protobuf:"bytes,5,opt,name=locality,proto3,oneof" json:"locality,omitempty"` // city, town, village, municipality + Area *string `protobuf:"bytes,6,opt,name=area,proto3,oneof" json:"area,omitempty"` // suburb, road, neighbourhood unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *MediaItemPlaceRequest) Reset() { *x = MediaItemPlaceRequest{} - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +679,7 @@ func (x *MediaItemPlaceRequest) String() string { func (*MediaItemPlaceRequest) ProtoMessage() {} func (x *MediaItemPlaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -386,7 +692,7 @@ func (x *MediaItemPlaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MediaItemPlaceRequest.ProtoReflect.Descriptor instead. func (*MediaItemPlaceRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} + return file_api_proto_rawDescGZIP(), []int{4} } func (x *MediaItemPlaceRequest) GetUserId() string { @@ -396,9 +702,9 @@ func (x *MediaItemPlaceRequest) GetUserId() string { return "" } -func (x *MediaItemPlaceRequest) GetId() string { +func (x *MediaItemPlaceRequest) GetMediaItemId() string { if x != nil { - return x.Id + return x.MediaItemId } return "" } @@ -417,83 +723,16 @@ func (x *MediaItemPlaceRequest) GetCountry() string { return "" } -func (x *MediaItemPlaceRequest) GetState() string { - if x != nil && x.State != nil { - return *x.State +func (x *MediaItemPlaceRequest) GetLocality() string { + if x != nil && x.Locality != nil { + return *x.Locality } return "" } -func (x *MediaItemPlaceRequest) GetCity() string { - if x != nil && x.City != nil { - return *x.City - } - return "" -} - -func (x *MediaItemPlaceRequest) GetTown() string { - if x != nil && x.Town != nil { - return *x.Town - } - return "" -} - -type MediaItemThingRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MediaItemThingRequest) Reset() { - *x = MediaItemThingRequest{} - mi := &file_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MediaItemThingRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MediaItemThingRequest) ProtoMessage() {} - -func (x *MediaItemThingRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MediaItemThingRequest.ProtoReflect.Descriptor instead. -func (*MediaItemThingRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} -} - -func (x *MediaItemThingRequest) GetUserId() string { - if x != nil { - return x.UserId - } - return "" -} - -func (x *MediaItemThingRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *MediaItemThingRequest) GetName() string { - if x != nil { - return x.Name +func (x *MediaItemPlaceRequest) GetArea() string { + if x != nil && x.Area != nil { + return *x.Area } return "" } @@ -545,7 +784,7 @@ func (x *MediaItemEmbedding) GetEmbedding() []float32 { type MediaItemFacesRequest struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + MediaItemId string `protobuf:"bytes,2,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` Embeddings []*MediaItemEmbedding `protobuf:"bytes,3,rep,name=embeddings,proto3" json:"embeddings,omitempty"` Thumbnails []string `protobuf:"bytes,4,rep,name=thumbnails,proto3" json:"thumbnails,omitempty"` unknownFields protoimpl.UnknownFields @@ -589,9 +828,9 @@ func (x *MediaItemFacesRequest) GetUserId() string { return "" } -func (x *MediaItemFacesRequest) GetId() string { +func (x *MediaItemFacesRequest) GetMediaItemId() string { if x != nil { - return x.Id + return x.MediaItemId } return "" } @@ -612,10 +851,12 @@ func (x *MediaItemFacesRequest) GetThumbnails() []string { type MediaItemFinalResultRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - Keywords string `protobuf:"bytes,3,opt,name=keywords,proto3" json:"keywords,omitempty"` - Embeddings []*MediaItemEmbedding `protobuf:"bytes,4,rep,name=embeddings,proto3" json:"embeddings,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=userId,proto3" json:"userId,omitempty"` + MediaItemId string `protobuf:"bytes,3,opt,name=mediaItemId,proto3" json:"mediaItemId,omitempty"` + DetectedText string `protobuf:"bytes,4,opt,name=detectedText,proto3" json:"detectedText,omitempty"` + Caption string `protobuf:"bytes,5,opt,name=caption,proto3" json:"caption,omitempty"` + Embeddings []*MediaItemEmbedding `protobuf:"bytes,6,rep,name=embeddings,proto3" json:"embeddings,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -650,6 +891,13 @@ func (*MediaItemFinalResultRequest) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{7} } +func (x *MediaItemFinalResultRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + func (x *MediaItemFinalResultRequest) GetUserId() string { if x != nil { return x.UserId @@ -657,16 +905,23 @@ func (x *MediaItemFinalResultRequest) GetUserId() string { return "" } -func (x *MediaItemFinalResultRequest) GetId() string { +func (x *MediaItemFinalResultRequest) GetMediaItemId() string { if x != nil { - return x.Id + return x.MediaItemId + } + return "" +} + +func (x *MediaItemFinalResultRequest) GetDetectedText() string { + if x != nil { + return x.DetectedText } return "" } -func (x *MediaItemFinalResultRequest) GetKeywords() string { +func (x *MediaItemFinalResultRequest) GetCaption() string { if x != nil { - return x.Keywords + return x.Caption } return "" } @@ -834,27 +1089,27 @@ func (x *MediaItemFaceEmbeddingsResponse) GetMediaItemFaceEmbeddings() []*MediaI return nil } -type GetUsersResponse struct { +type UsersResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Users []string `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetUsersResponse) Reset() { - *x = GetUsersResponse{} +func (x *UsersResponse) Reset() { + *x = UsersResponse{} mi := &file_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetUsersResponse) String() string { +func (x *UsersResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetUsersResponse) ProtoMessage() {} +func (*UsersResponse) ProtoMessage() {} -func (x *GetUsersResponse) ProtoReflect() protoreflect.Message { +func (x *UsersResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -866,12 +1121,12 @@ func (x *GetUsersResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetUsersResponse.ProtoReflect.Descriptor instead. -func (*GetUsersResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use UsersResponse.ProtoReflect.Descriptor instead. +func (*UsersResponse) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{11} } -func (x *GetUsersResponse) GetUsers() []string { +func (x *UsersResponse) GetUsers() []string { if x != nil { return x.Users } @@ -976,228 +1231,177 @@ func (x *MediaItemPeopleRequest) GetMediaItemFacePeople() map[string]*MediaItemF var File_api_proto protoreflect.FileDescriptor -var file_api_proto_rawDesc = string([]byte{ - 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x28, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x22, 0xc7, 0x06, 0x0a, 0x18, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1f, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, - 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, - 0x12, 0x19, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x48, - 0x01, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, - 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x4d, 0x61, 0x6b, 0x65, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x0a, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x4d, - 0x61, 0x6b, 0x65, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, - 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x0b, 0x63, - 0x61, 0x6d, 0x65, 0x72, 0x61, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, - 0x0b, 0x66, 0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x06, 0x52, 0x0b, 0x66, 0x6f, 0x63, 0x61, 0x6c, 0x4c, 0x65, 0x6e, 0x67, 0x74, - 0x68, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x61, 0x70, 0x65, 0x72, 0x74, 0x75, 0x72, 0x65, - 0x46, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x48, 0x07, 0x52, - 0x0f, 0x61, 0x70, 0x65, 0x72, 0x74, 0x75, 0x72, 0x65, 0x46, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, - 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0d, 0x69, 0x73, 0x6f, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, - 0x6c, 0x65, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x48, 0x08, 0x52, 0x0d, 0x69, 0x73, - 0x6f, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x27, - 0x0a, 0x0c, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x75, 0x72, 0x65, - 0x54, 0x69, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x66, 0x70, 0x73, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x03, 0x66, 0x70, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1f, - 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x01, - 0x48, 0x0b, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, - 0x21, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x12, 0x20, 0x01, - 0x28, 0x01, 0x48, 0x0c, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x88, - 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x65, 0x78, 0x69, 0x66, 0x44, 0x61, 0x74, 0x61, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x0d, 0x52, 0x08, 0x65, 0x78, 0x69, 0x66, 0x44, 0x61, 0x74, 0x61, - 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x61, 0x6d, 0x65, 0x72, - 0x61, 0x4d, 0x61, 0x6b, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x63, 0x61, 0x6d, 0x65, 0x72, 0x61, - 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x66, 0x6f, 0x63, 0x61, 0x6c, 0x4c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x61, 0x70, 0x65, 0x72, 0x74, 0x75, - 0x72, 0x65, 0x46, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x69, 0x73, - 0x6f, 0x45, 0x71, 0x75, 0x69, 0x76, 0x61, 0x6c, 0x65, 0x6e, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, - 0x65, 0x78, 0x70, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x06, 0x0a, 0x04, - 0x5f, 0x66, 0x70, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x65, 0x78, 0x69, 0x66, 0x44, 0x61, 0x74, 0x61, 0x22, 0xc1, 0x02, 0x0a, - 0x20, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, - 0x77, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x23, 0x0a, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, - 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, - 0x77, 0x50, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x70, - 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x50, 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, - 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x50, 0x61, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x70, 0x6c, 0x61, 0x63, - 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, - 0x0b, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x42, - 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x42, 0x0e, - 0x0a, 0x0c, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x50, 0x61, 0x74, 0x68, 0x42, 0x10, - 0x0a, 0x0e, 0x5f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x50, 0x61, 0x74, 0x68, - 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x68, 0x6f, 0x6c, 0x64, 0x65, 0x72, - 0x22, 0x81, 0x02, 0x0a, 0x15, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x6c, - 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x74, 0x63, 0x6f, 0x64, 0x65, - 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x88, - 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x02, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, - 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x04, 0x63, - 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x74, 0x6f, 0x77, 0x6e, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x04, 0x74, 0x6f, 0x77, 0x6e, 0x88, 0x01, 0x01, 0x42, - 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x6f, 0x73, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x0a, 0x0a, 0x08, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x63, 0x69, 0x74, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x5f, - 0x74, 0x6f, 0x77, 0x6e, 0x22, 0x53, 0x0a, 0x15, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, - 0x6d, 0x54, 0x68, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x12, 0x4d, 0x65, 0x64, - 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, - 0x1c, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x02, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x94, 0x01, - 0x0a, 0x15, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x33, 0x0a, 0x0a, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x45, - 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x0a, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x1b, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, - 0x65, 0x6d, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, - 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x33, 0x0a, 0x0a, 0x65, 0x6d, 0x62, 0x65, - 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4d, - 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x0a, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x38, 0x0a, - 0x1e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, - 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x99, 0x01, 0x0a, 0x16, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, - 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x49, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, - 0x65, 0x6d, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x49, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x49, 0x64, - 0x12, 0x31, 0x0a, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x45, - 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x09, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, - 0x69, 0x6e, 0x67, 0x22, 0x74, 0x0a, 0x1f, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, - 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, - 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, - 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, - 0x52, 0x17, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, - 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x28, 0x0a, 0x10, 0x47, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x75, 0x73, - 0x65, 0x72, 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x13, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, - 0x6d, 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x66, - 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x24, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x50, - 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x66, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, - 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0xf2, 0x01, 0x0a, 0x16, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x65, - 0x6f, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x62, 0x0a, 0x13, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, - 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x30, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x65, 0x6f, 0x70, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, - 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x13, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, - 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x1a, 0x5c, 0x0a, 0x18, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, - 0x46, 0x61, 0x63, 0x65, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x81, 0x06, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x3c, 0x0a, - 0x0f, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x1a, 0x47, - 0x65, 0x74, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, - 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1f, 0x2e, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, - 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x4d, 0x65, 0x64, - 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, - 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, - 0x0a, 0x08, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x61, 0x76, 0x65, 0x4d, - 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x19, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5c, 0x0a, 0x1d, 0x53, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, - 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x54, 0x68, 0x75, - 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x21, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, - 0x65, 0x6d, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, - 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x12, 0x53, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x49, 0x74, 0x65, 0x6d, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x12, 0x16, 0x2e, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x12, 0x53, - 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x54, 0x68, 0x69, 0x6e, - 0x67, 0x12, 0x16, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x54, 0x68, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x12, 0x53, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x13, 0x53, - 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x65, 0x6f, 0x70, - 0x6c, 0x65, 0x12, 0x17, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x65, - 0x6f, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x18, 0x53, 0x61, 0x76, 0x65, 0x4d, 0x65, 0x64, - 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x1c, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x46, 0x69, 0x6e, - 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04, 0x61, 0x70, 0x69, - 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_api_proto_rawDesc = "" + + "\n" + + "\tapi.proto\x1a\x1bgoogle/protobuf/empty.proto\"(\n" + + "\x0eConfigResponse\x12\x16\n" + + "\x06config\x18\x01 \x01(\fR\x06config\"\x97\x02\n" + + "\x18MediaItemProcessResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + + "\x06userId\x18\x02 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x03 \x01(\tR\vmediaItemId\x123\n" + + "\n" + + "components\x18\x04 \x03(\x0e2\x13.MediaItemComponentR\n" + + "components\x12@\n" + + "\apayload\x18\x05 \x03(\v2&.MediaItemProcessResponse.PayloadEntryR\apayload\x1a:\n" + + "\fPayloadEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc3\a\n" + + "\x18MediaItemMetadataRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x02 \x01(\tR\vmediaItemId\x12(\n" + + "\x06status\x18\x03 \x01(\x0e2\x10.MediaItemStatusR\x06status\x12\x1f\n" + + "\bmimeType\x18\x04 \x01(\tH\x00R\bmimeType\x88\x01\x01\x12\"\n" + + "\x04type\x18\x05 \x01(\x0e2\x0e.MediaItemTypeR\x04type\x12.\n" + + "\bcategory\x18\x06 \x01(\x0e2\x12.MediaItemCategoryR\bcategory\x12\x19\n" + + "\x05width\x18\a \x01(\x05H\x01R\x05width\x88\x01\x01\x12\x1b\n" + + "\x06height\x18\b \x01(\x05H\x02R\x06height\x88\x01\x01\x12'\n" + + "\fcreationTime\x18\t \x01(\tH\x03R\fcreationTime\x88\x01\x01\x12#\n" + + "\n" + + "cameraMake\x18\n" + + " \x01(\tH\x04R\n" + + "cameraMake\x88\x01\x01\x12%\n" + + "\vcameraModel\x18\v \x01(\tH\x05R\vcameraModel\x88\x01\x01\x12%\n" + + "\vfocalLength\x18\f \x01(\tH\x06R\vfocalLength\x88\x01\x01\x12-\n" + + "\x0fapertureFNumber\x18\r \x01(\tH\aR\x0fapertureFNumber\x88\x01\x01\x12)\n" + + "\risoEquivalent\x18\x0e \x01(\tH\bR\risoEquivalent\x88\x01\x01\x12'\n" + + "\fexposureTime\x18\x0f \x01(\tH\tR\fexposureTime\x88\x01\x01\x12#\n" + + "\n" + + "megapixels\x18\x10 \x01(\tH\n" + + "R\n" + + "megapixels\x88\x01\x01\x12\x15\n" + + "\x03fps\x18\x11 \x01(\tH\vR\x03fps\x88\x01\x01\x12\x1f\n" + + "\blatitude\x18\x12 \x01(\x01H\fR\blatitude\x88\x01\x01\x12!\n" + + "\tlongitude\x18\x13 \x01(\x01H\rR\tlongitude\x88\x01\x01\x12\x1f\n" + + "\bexifData\x18\x14 \x01(\tH\x0eR\bexifData\x88\x01\x01B\v\n" + + "\t_mimeTypeB\b\n" + + "\x06_widthB\t\n" + + "\a_heightB\x0f\n" + + "\r_creationTimeB\r\n" + + "\v_cameraMakeB\x0e\n" + + "\f_cameraModelB\x0e\n" + + "\f_focalLengthB\x12\n" + + "\x10_apertureFNumberB\x10\n" + + "\x0e_isoEquivalentB\x0f\n" + + "\r_exposureTimeB\r\n" + + "\v_megapixelsB\x06\n" + + "\x04_fpsB\v\n" + + "\t_latitudeB\f\n" + + "\n" + + "_longitudeB\v\n" + + "\t_exifData\"\xe5\x02\n" + + " MediaItemPreviewThumbnailRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x02 \x01(\tR\vmediaItemId\x12(\n" + + "\x06status\x18\x03 \x01(\x0e2\x10.MediaItemStatusR\x06status\x12#\n" + + "\n" + + "sourcePath\x18\x04 \x01(\tH\x00R\n" + + "sourcePath\x88\x01\x01\x12%\n" + + "\vpreviewPath\x18\x05 \x01(\tH\x01R\vpreviewPath\x88\x01\x01\x12)\n" + + "\rthumbnailPath\x18\x06 \x01(\tH\x02R\rthumbnailPath\x88\x01\x01\x12%\n" + + "\vplaceholder\x18\a \x01(\tH\x03R\vplaceholder\x88\x01\x01B\r\n" + + "\v_sourcePathB\x0e\n" + + "\f_previewPathB\x10\n" + + "\x0e_thumbnailPathB\x0e\n" + + "\f_placeholder\"\xfa\x01\n" + + "\x15MediaItemPlaceRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x02 \x01(\tR\vmediaItemId\x12\x1f\n" + + "\bpostcode\x18\x03 \x01(\tH\x00R\bpostcode\x88\x01\x01\x12\x1d\n" + + "\acountry\x18\x04 \x01(\tH\x01R\acountry\x88\x01\x01\x12\x1f\n" + + "\blocality\x18\x05 \x01(\tH\x02R\blocality\x88\x01\x01\x12\x17\n" + + "\x04area\x18\x06 \x01(\tH\x03R\x04area\x88\x01\x01B\v\n" + + "\t_postcodeB\n" + + "\n" + + "\b_countryB\v\n" + + "\t_localityB\a\n" + + "\x05_area\"2\n" + + "\x12MediaItemEmbedding\x12\x1c\n" + + "\tembedding\x18\x01 \x03(\x02R\tembedding\"\xa6\x01\n" + + "\x15MediaItemFacesRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x02 \x01(\tR\vmediaItemId\x123\n" + + "\n" + + "embeddings\x18\x03 \x03(\v2\x13.MediaItemEmbeddingR\n" + + "embeddings\x12\x1e\n" + + "\n" + + "thumbnails\x18\x04 \x03(\tR\n" + + "thumbnails\"\xda\x01\n" + + "\x1bMediaItemFinalResultRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x16\n" + + "\x06userId\x18\x02 \x01(\tR\x06userId\x12 \n" + + "\vmediaItemId\x18\x03 \x01(\tR\vmediaItemId\x12\"\n" + + "\fdetectedText\x18\x04 \x01(\tR\fdetectedText\x12\x18\n" + + "\acaption\x18\x05 \x01(\tR\acaption\x123\n" + + "\n" + + "embeddings\x18\x06 \x03(\v2\x13.MediaItemEmbeddingR\n" + + "embeddings\"8\n" + + "\x1eMediaItemFaceEmbeddingsRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\"\x99\x01\n" + + "\x16MediaItemFaceEmbedding\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12 \n" + + "\vmediaItemId\x18\x02 \x01(\tR\vmediaItemId\x12\x1a\n" + + "\bpeopleId\x18\x03 \x01(\tR\bpeopleId\x121\n" + + "\tembedding\x18\x04 \x01(\v2\x13.MediaItemEmbeddingR\tembedding\"t\n" + + "\x1fMediaItemFaceEmbeddingsResponse\x12Q\n" + + "\x17mediaItemFaceEmbeddings\x18\x01 \x03(\v2\x17.MediaItemFaceEmbeddingR\x17mediaItemFaceEmbeddings\"%\n" + + "\rUsersResponse\x12\x14\n" + + "\x05users\x18\x01 \x03(\tR\x05users\"\x9a\x01\n" + + "\x13MediaItemFacePeople\x12D\n" + + "\n" + + "facePeople\x18\x01 \x03(\v2$.MediaItemFacePeople.FacePeopleEntryR\n" + + "facePeople\x1a=\n" + + "\x0fFacePeopleEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf2\x01\n" + + "\x16MediaItemPeopleRequest\x12\x16\n" + + "\x06userId\x18\x01 \x01(\tR\x06userId\x12b\n" + + "\x13mediaItemFacePeople\x18\x02 \x03(\v20.MediaItemPeopleRequest.MediaItemFacePeopleEntryR\x13mediaItemFacePeople\x1a\\\n" + + "\x18MediaItemFacePeopleEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12*\n" + + "\x05value\x18\x02 \x01(\v2\x14.MediaItemFacePeopleR\x05value:\x028\x01*y\n" + + "\x12MediaItemComponent\x12\f\n" + + "\bMETADATA\x10\x00\x12\x15\n" + + "\x11PREVIEW_THUMBNAIL\x10\x01\x12\n" + + "\n" + + "\x06PLACES\x10\x02\x12\x12\n" + + "\x0eCLASSIFICATION\x10\x03\x12\t\n" + + "\x05FACES\x10\x04\x12\a\n" + + "\x03OCR\x10\x05\x12\n" + + "\n" + + "\x06SEARCH\x10\x06*I\n" + + "\x0fMediaItemStatus\x12\x0f\n" + + "\vUNSPECIFIED\x10\x00\x12\x0e\n" + + "\n" + + "PROCESSING\x10\x01\x12\t\n" + + "\x05READY\x10\x02\x12\n" + + "\n" + + "\x06FAILED\x10\x03*2\n" + + "\rMediaItemType\x12\v\n" + + "\aUNKNOWN\x10\x00\x12\t\n" + + "\x05PHOTO\x10\x01\x12\t\n" + + "\x05VIDEO\x10\x02*m\n" + + "\x11MediaItemCategory\x12\v\n" + + "\aDEFAULT\x10\x00\x12\x0e\n" + + "\n" + + "SCREENSHOT\x10\x01\x12\f\n" + + "\bPANORAMA\x10\x02\x12\b\n" + + "\x04SLOW\x10\x03\x12\n" + + "\n" + + "\x06MOTION\x10\x04\x12\b\n" + + "\x04LIVE\x10\x05\x12\r\n" + + "\tTIMELAPSE\x10\x062\x82\x06\n" + + "\x03API\x12<\n" + + "\x0fGetWorkerConfig\x12\x16.google.protobuf.Empty\x1a\x0f.ConfigResponse\"\x00\x12J\n" + + "\x13GetMediaItemProcess\x12\x16.google.protobuf.Empty\x1a\x19.MediaItemProcessResponse\"\x00\x12a\n" + + "\x1aGetMediaItemFaceEmbeddings\x12\x1f.MediaItemFaceEmbeddingsRequest\x1a .MediaItemFaceEmbeddingsResponse\"\x00\x124\n" + + "\bGetUsers\x12\x16.google.protobuf.Empty\x1a\x0e.UsersResponse\"\x00\x12L\n" + + "\x15SaveMediaItemMetadata\x12\x19.MediaItemMetadataRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\\\n" + + "\x1dSaveMediaItemPreviewThumbnail\x12!.MediaItemPreviewThumbnailRequest\x1a\x16.google.protobuf.Empty\"\x00\x12F\n" + + "\x12SaveMediaItemPlace\x12\x16.MediaItemPlaceRequest\x1a\x16.google.protobuf.Empty\"\x00\x12F\n" + + "\x12SaveMediaItemFaces\x12\x16.MediaItemFacesRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n" + + "\x13SaveMediaItemPeople\x12\x17.MediaItemPeopleRequest\x1a\x16.google.protobuf.Empty\"\x00\x12R\n" + + "\x18SaveMediaItemFinalResult\x12\x1c.MediaItemFinalResultRequest\x1a\x16.google.protobuf.Empty\"\x00B\x06Z\x04api/b\x06proto3" var ( file_api_proto_rawDescOnce sync.Once @@ -1211,59 +1415,71 @@ func file_api_proto_rawDescGZIP() []byte { return file_api_proto_rawDescData } -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_api_proto_goTypes = []any{ - (*ConfigResponse)(nil), // 0: ConfigResponse - (*MediaItemMetadataRequest)(nil), // 1: MediaItemMetadataRequest - (*MediaItemPreviewThumbnailRequest)(nil), // 2: MediaItemPreviewThumbnailRequest - (*MediaItemPlaceRequest)(nil), // 3: MediaItemPlaceRequest - (*MediaItemThingRequest)(nil), // 4: MediaItemThingRequest - (*MediaItemEmbedding)(nil), // 5: MediaItemEmbedding - (*MediaItemFacesRequest)(nil), // 6: MediaItemFacesRequest - (*MediaItemFinalResultRequest)(nil), // 7: MediaItemFinalResultRequest - (*MediaItemFaceEmbeddingsRequest)(nil), // 8: MediaItemFaceEmbeddingsRequest - (*MediaItemFaceEmbedding)(nil), // 9: MediaItemFaceEmbedding - (*MediaItemFaceEmbeddingsResponse)(nil), // 10: MediaItemFaceEmbeddingsResponse - (*GetUsersResponse)(nil), // 11: GetUsersResponse - (*MediaItemFacePeople)(nil), // 12: MediaItemFacePeople - (*MediaItemPeopleRequest)(nil), // 13: MediaItemPeopleRequest - nil, // 14: MediaItemFacePeople.FacePeopleEntry - nil, // 15: MediaItemPeopleRequest.MediaItemFacePeopleEntry - (*emptypb.Empty)(nil), // 16: google.protobuf.Empty + (MediaItemComponent)(0), // 0: MediaItemComponent + (MediaItemStatus)(0), // 1: MediaItemStatus + (MediaItemType)(0), // 2: MediaItemType + (MediaItemCategory)(0), // 3: MediaItemCategory + (*ConfigResponse)(nil), // 4: ConfigResponse + (*MediaItemProcessResponse)(nil), // 5: MediaItemProcessResponse + (*MediaItemMetadataRequest)(nil), // 6: MediaItemMetadataRequest + (*MediaItemPreviewThumbnailRequest)(nil), // 7: MediaItemPreviewThumbnailRequest + (*MediaItemPlaceRequest)(nil), // 8: MediaItemPlaceRequest + (*MediaItemEmbedding)(nil), // 9: MediaItemEmbedding + (*MediaItemFacesRequest)(nil), // 10: MediaItemFacesRequest + (*MediaItemFinalResultRequest)(nil), // 11: MediaItemFinalResultRequest + (*MediaItemFaceEmbeddingsRequest)(nil), // 12: MediaItemFaceEmbeddingsRequest + (*MediaItemFaceEmbedding)(nil), // 13: MediaItemFaceEmbedding + (*MediaItemFaceEmbeddingsResponse)(nil), // 14: MediaItemFaceEmbeddingsResponse + (*UsersResponse)(nil), // 15: UsersResponse + (*MediaItemFacePeople)(nil), // 16: MediaItemFacePeople + (*MediaItemPeopleRequest)(nil), // 17: MediaItemPeopleRequest + nil, // 18: MediaItemProcessResponse.PayloadEntry + nil, // 19: MediaItemFacePeople.FacePeopleEntry + nil, // 20: MediaItemPeopleRequest.MediaItemFacePeopleEntry + (*emptypb.Empty)(nil), // 21: google.protobuf.Empty } var file_api_proto_depIdxs = []int32{ - 5, // 0: MediaItemFacesRequest.embeddings:type_name -> MediaItemEmbedding - 5, // 1: MediaItemFinalResultRequest.embeddings:type_name -> MediaItemEmbedding - 5, // 2: MediaItemFaceEmbedding.embedding:type_name -> MediaItemEmbedding - 9, // 3: MediaItemFaceEmbeddingsResponse.mediaItemFaceEmbeddings:type_name -> MediaItemFaceEmbedding - 14, // 4: MediaItemFacePeople.facePeople:type_name -> MediaItemFacePeople.FacePeopleEntry - 15, // 5: MediaItemPeopleRequest.mediaItemFacePeople:type_name -> MediaItemPeopleRequest.MediaItemFacePeopleEntry - 12, // 6: MediaItemPeopleRequest.MediaItemFacePeopleEntry.value:type_name -> MediaItemFacePeople - 16, // 7: API.GetWorkerConfig:input_type -> google.protobuf.Empty - 8, // 8: API.GetMediaItemFaceEmbeddings:input_type -> MediaItemFaceEmbeddingsRequest - 16, // 9: API.GetUsers:input_type -> google.protobuf.Empty - 1, // 10: API.SaveMediaItemMetadata:input_type -> MediaItemMetadataRequest - 2, // 11: API.SaveMediaItemPreviewThumbnail:input_type -> MediaItemPreviewThumbnailRequest - 3, // 12: API.SaveMediaItemPlace:input_type -> MediaItemPlaceRequest - 4, // 13: API.SaveMediaItemThing:input_type -> MediaItemThingRequest - 6, // 14: API.SaveMediaItemFaces:input_type -> MediaItemFacesRequest - 13, // 15: API.SaveMediaItemPeople:input_type -> MediaItemPeopleRequest - 7, // 16: API.SaveMediaItemFinalResult:input_type -> MediaItemFinalResultRequest - 0, // 17: API.GetWorkerConfig:output_type -> ConfigResponse - 10, // 18: API.GetMediaItemFaceEmbeddings:output_type -> MediaItemFaceEmbeddingsResponse - 11, // 19: API.GetUsers:output_type -> GetUsersResponse - 16, // 20: API.SaveMediaItemMetadata:output_type -> google.protobuf.Empty - 16, // 21: API.SaveMediaItemPreviewThumbnail:output_type -> google.protobuf.Empty - 16, // 22: API.SaveMediaItemPlace:output_type -> google.protobuf.Empty - 16, // 23: API.SaveMediaItemThing:output_type -> google.protobuf.Empty - 16, // 24: API.SaveMediaItemFaces:output_type -> google.protobuf.Empty - 16, // 25: API.SaveMediaItemPeople:output_type -> google.protobuf.Empty - 16, // 26: API.SaveMediaItemFinalResult:output_type -> google.protobuf.Empty - 17, // [17:27] is the sub-list for method output_type - 7, // [7:17] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 0, // 0: MediaItemProcessResponse.components:type_name -> MediaItemComponent + 18, // 1: MediaItemProcessResponse.payload:type_name -> MediaItemProcessResponse.PayloadEntry + 1, // 2: MediaItemMetadataRequest.status:type_name -> MediaItemStatus + 2, // 3: MediaItemMetadataRequest.type:type_name -> MediaItemType + 3, // 4: MediaItemMetadataRequest.category:type_name -> MediaItemCategory + 1, // 5: MediaItemPreviewThumbnailRequest.status:type_name -> MediaItemStatus + 9, // 6: MediaItemFacesRequest.embeddings:type_name -> MediaItemEmbedding + 9, // 7: MediaItemFinalResultRequest.embeddings:type_name -> MediaItemEmbedding + 9, // 8: MediaItemFaceEmbedding.embedding:type_name -> MediaItemEmbedding + 13, // 9: MediaItemFaceEmbeddingsResponse.mediaItemFaceEmbeddings:type_name -> MediaItemFaceEmbedding + 19, // 10: MediaItemFacePeople.facePeople:type_name -> MediaItemFacePeople.FacePeopleEntry + 20, // 11: MediaItemPeopleRequest.mediaItemFacePeople:type_name -> MediaItemPeopleRequest.MediaItemFacePeopleEntry + 16, // 12: MediaItemPeopleRequest.MediaItemFacePeopleEntry.value:type_name -> MediaItemFacePeople + 21, // 13: API.GetWorkerConfig:input_type -> google.protobuf.Empty + 21, // 14: API.GetMediaItemProcess:input_type -> google.protobuf.Empty + 12, // 15: API.GetMediaItemFaceEmbeddings:input_type -> MediaItemFaceEmbeddingsRequest + 21, // 16: API.GetUsers:input_type -> google.protobuf.Empty + 6, // 17: API.SaveMediaItemMetadata:input_type -> MediaItemMetadataRequest + 7, // 18: API.SaveMediaItemPreviewThumbnail:input_type -> MediaItemPreviewThumbnailRequest + 8, // 19: API.SaveMediaItemPlace:input_type -> MediaItemPlaceRequest + 10, // 20: API.SaveMediaItemFaces:input_type -> MediaItemFacesRequest + 17, // 21: API.SaveMediaItemPeople:input_type -> MediaItemPeopleRequest + 11, // 22: API.SaveMediaItemFinalResult:input_type -> MediaItemFinalResultRequest + 4, // 23: API.GetWorkerConfig:output_type -> ConfigResponse + 5, // 24: API.GetMediaItemProcess:output_type -> MediaItemProcessResponse + 14, // 25: API.GetMediaItemFaceEmbeddings:output_type -> MediaItemFaceEmbeddingsResponse + 15, // 26: API.GetUsers:output_type -> UsersResponse + 21, // 27: API.SaveMediaItemMetadata:output_type -> google.protobuf.Empty + 21, // 28: API.SaveMediaItemPreviewThumbnail:output_type -> google.protobuf.Empty + 21, // 29: API.SaveMediaItemPlace:output_type -> google.protobuf.Empty + 21, // 30: API.SaveMediaItemFaces:output_type -> google.protobuf.Empty + 21, // 31: API.SaveMediaItemPeople:output_type -> google.protobuf.Empty + 21, // 32: API.SaveMediaItemFinalResult:output_type -> google.protobuf.Empty + 23, // [23:33] is the sub-list for method output_type + 13, // [13:23] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -1271,21 +1487,22 @@ func file_api_proto_init() { if File_api_proto != nil { return } - file_api_proto_msgTypes[1].OneofWrappers = []any{} file_api_proto_msgTypes[2].OneofWrappers = []any{} file_api_proto_msgTypes[3].OneofWrappers = []any{} + file_api_proto_msgTypes[4].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc)), - NumEnums: 0, - NumMessages: 16, + NumEnums: 4, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, GoTypes: file_api_proto_goTypes, DependencyIndexes: file_api_proto_depIdxs, + EnumInfos: file_api_proto_enumTypes, MessageInfos: file_api_proto_msgTypes, }.Build() File_api_proto = out.File diff --git a/api/pkg/services/api/api_grpc.pb.go b/api/pkg/services/api/api_grpc.pb.go index 054f4cd8..2b9959ff 100644 --- a/api/pkg/services/api/api_grpc.pb.go +++ b/api/pkg/services/api/api_grpc.pb.go @@ -21,12 +21,12 @@ const _ = grpc.SupportPackageIsVersion9 const ( API_GetWorkerConfig_FullMethodName = "/API/GetWorkerConfig" + API_GetMediaItemProcess_FullMethodName = "/API/GetMediaItemProcess" API_GetMediaItemFaceEmbeddings_FullMethodName = "/API/GetMediaItemFaceEmbeddings" API_GetUsers_FullMethodName = "/API/GetUsers" API_SaveMediaItemMetadata_FullMethodName = "/API/SaveMediaItemMetadata" API_SaveMediaItemPreviewThumbnail_FullMethodName = "/API/SaveMediaItemPreviewThumbnail" API_SaveMediaItemPlace_FullMethodName = "/API/SaveMediaItemPlace" - API_SaveMediaItemThing_FullMethodName = "/API/SaveMediaItemThing" API_SaveMediaItemFaces_FullMethodName = "/API/SaveMediaItemFaces" API_SaveMediaItemPeople_FullMethodName = "/API/SaveMediaItemPeople" API_SaveMediaItemFinalResult_FullMethodName = "/API/SaveMediaItemFinalResult" @@ -37,12 +37,12 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type APIClient interface { GetWorkerConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfigResponse, error) + GetMediaItemProcess(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MediaItemProcessResponse, error) GetMediaItemFaceEmbeddings(ctx context.Context, in *MediaItemFaceEmbeddingsRequest, opts ...grpc.CallOption) (*MediaItemFaceEmbeddingsResponse, error) - GetUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetUsersResponse, error) + GetUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*UsersResponse, error) SaveMediaItemMetadata(ctx context.Context, in *MediaItemMetadataRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) SaveMediaItemPreviewThumbnail(ctx context.Context, in *MediaItemPreviewThumbnailRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) SaveMediaItemPlace(ctx context.Context, in *MediaItemPlaceRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - SaveMediaItemThing(ctx context.Context, in *MediaItemThingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) SaveMediaItemFaces(ctx context.Context, in *MediaItemFacesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) SaveMediaItemPeople(ctx context.Context, in *MediaItemPeopleRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) SaveMediaItemFinalResult(ctx context.Context, in *MediaItemFinalResultRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -66,6 +66,16 @@ func (c *aPIClient) GetWorkerConfig(ctx context.Context, in *emptypb.Empty, opts return out, nil } +func (c *aPIClient) GetMediaItemProcess(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MediaItemProcessResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MediaItemProcessResponse) + err := c.cc.Invoke(ctx, API_GetMediaItemProcess_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *aPIClient) GetMediaItemFaceEmbeddings(ctx context.Context, in *MediaItemFaceEmbeddingsRequest, opts ...grpc.CallOption) (*MediaItemFaceEmbeddingsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(MediaItemFaceEmbeddingsResponse) @@ -76,9 +86,9 @@ func (c *aPIClient) GetMediaItemFaceEmbeddings(ctx context.Context, in *MediaIte return out, nil } -func (c *aPIClient) GetUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetUsersResponse, error) { +func (c *aPIClient) GetUsers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*UsersResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetUsersResponse) + out := new(UsersResponse) err := c.cc.Invoke(ctx, API_GetUsers_FullMethodName, in, out, cOpts...) if err != nil { return nil, err @@ -116,16 +126,6 @@ func (c *aPIClient) SaveMediaItemPlace(ctx context.Context, in *MediaItemPlaceRe return out, nil } -func (c *aPIClient) SaveMediaItemThing(ctx context.Context, in *MediaItemThingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, API_SaveMediaItemThing_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *aPIClient) SaveMediaItemFaces(ctx context.Context, in *MediaItemFacesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) @@ -161,12 +161,12 @@ func (c *aPIClient) SaveMediaItemFinalResult(ctx context.Context, in *MediaItemF // for forward compatibility. type APIServer interface { GetWorkerConfig(context.Context, *emptypb.Empty) (*ConfigResponse, error) + GetMediaItemProcess(context.Context, *emptypb.Empty) (*MediaItemProcessResponse, error) GetMediaItemFaceEmbeddings(context.Context, *MediaItemFaceEmbeddingsRequest) (*MediaItemFaceEmbeddingsResponse, error) - GetUsers(context.Context, *emptypb.Empty) (*GetUsersResponse, error) + GetUsers(context.Context, *emptypb.Empty) (*UsersResponse, error) SaveMediaItemMetadata(context.Context, *MediaItemMetadataRequest) (*emptypb.Empty, error) SaveMediaItemPreviewThumbnail(context.Context, *MediaItemPreviewThumbnailRequest) (*emptypb.Empty, error) SaveMediaItemPlace(context.Context, *MediaItemPlaceRequest) (*emptypb.Empty, error) - SaveMediaItemThing(context.Context, *MediaItemThingRequest) (*emptypb.Empty, error) SaveMediaItemFaces(context.Context, *MediaItemFacesRequest) (*emptypb.Empty, error) SaveMediaItemPeople(context.Context, *MediaItemPeopleRequest) (*emptypb.Empty, error) SaveMediaItemFinalResult(context.Context, *MediaItemFinalResultRequest) (*emptypb.Empty, error) @@ -183,10 +183,13 @@ type UnimplementedAPIServer struct{} func (UnimplementedAPIServer) GetWorkerConfig(context.Context, *emptypb.Empty) (*ConfigResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetWorkerConfig not implemented") } +func (UnimplementedAPIServer) GetMediaItemProcess(context.Context, *emptypb.Empty) (*MediaItemProcessResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMediaItemProcess not implemented") +} func (UnimplementedAPIServer) GetMediaItemFaceEmbeddings(context.Context, *MediaItemFaceEmbeddingsRequest) (*MediaItemFaceEmbeddingsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetMediaItemFaceEmbeddings not implemented") } -func (UnimplementedAPIServer) GetUsers(context.Context, *emptypb.Empty) (*GetUsersResponse, error) { +func (UnimplementedAPIServer) GetUsers(context.Context, *emptypb.Empty) (*UsersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetUsers not implemented") } func (UnimplementedAPIServer) SaveMediaItemMetadata(context.Context, *MediaItemMetadataRequest) (*emptypb.Empty, error) { @@ -198,9 +201,6 @@ func (UnimplementedAPIServer) SaveMediaItemPreviewThumbnail(context.Context, *Me func (UnimplementedAPIServer) SaveMediaItemPlace(context.Context, *MediaItemPlaceRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SaveMediaItemPlace not implemented") } -func (UnimplementedAPIServer) SaveMediaItemThing(context.Context, *MediaItemThingRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SaveMediaItemThing not implemented") -} func (UnimplementedAPIServer) SaveMediaItemFaces(context.Context, *MediaItemFacesRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SaveMediaItemFaces not implemented") } @@ -249,6 +249,24 @@ func _API_GetWorkerConfig_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _API_GetMediaItemProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(APIServer).GetMediaItemProcess(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: API_GetMediaItemProcess_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(APIServer).GetMediaItemProcess(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _API_GetMediaItemFaceEmbeddings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MediaItemFaceEmbeddingsRequest) if err := dec(in); err != nil { @@ -339,24 +357,6 @@ func _API_SaveMediaItemPlace_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } -func _API_SaveMediaItemThing_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MediaItemThingRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(APIServer).SaveMediaItemThing(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: API_SaveMediaItemThing_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(APIServer).SaveMediaItemThing(ctx, req.(*MediaItemThingRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _API_SaveMediaItemFaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MediaItemFacesRequest) if err := dec(in); err != nil { @@ -422,6 +422,10 @@ var API_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetWorkerConfig", Handler: _API_GetWorkerConfig_Handler, }, + { + MethodName: "GetMediaItemProcess", + Handler: _API_GetMediaItemProcess_Handler, + }, { MethodName: "GetMediaItemFaceEmbeddings", Handler: _API_GetMediaItemFaceEmbeddings_Handler, @@ -442,10 +446,6 @@ var API_ServiceDesc = grpc.ServiceDesc{ MethodName: "SaveMediaItemPlace", Handler: _API_SaveMediaItemPlace_Handler, }, - { - MethodName: "SaveMediaItemThing", - Handler: _API_SaveMediaItemThing_Handler, - }, { MethodName: "SaveMediaItemFaces", Handler: _API_SaveMediaItemFaces_Handler, diff --git a/api/pkg/services/worker/worker.pb.go b/api/pkg/services/worker/worker.pb.go index 8a302e98..2f396d1e 100644 --- a/api/pkg/services/worker/worker.pb.go +++ b/api/pkg/services/worker/worker.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.9 // protoc v5.29.3 // source: worker.proto @@ -21,187 +21,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type MediaItemComponent int32 - -const ( - MediaItemComponent_METADATA MediaItemComponent = 0 - MediaItemComponent_PREVIEW_THUMBNAIL MediaItemComponent = 1 - MediaItemComponent_PLACES MediaItemComponent = 2 - MediaItemComponent_CLASSIFICATION MediaItemComponent = 3 - MediaItemComponent_FACES MediaItemComponent = 4 - MediaItemComponent_OCR MediaItemComponent = 5 - MediaItemComponent_SEARCH MediaItemComponent = 6 -) - -// Enum value maps for MediaItemComponent. -var ( - MediaItemComponent_name = map[int32]string{ - 0: "METADATA", - 1: "PREVIEW_THUMBNAIL", - 2: "PLACES", - 3: "CLASSIFICATION", - 4: "FACES", - 5: "OCR", - 6: "SEARCH", - } - MediaItemComponent_value = map[string]int32{ - "METADATA": 0, - "PREVIEW_THUMBNAIL": 1, - "PLACES": 2, - "CLASSIFICATION": 3, - "FACES": 4, - "OCR": 5, - "SEARCH": 6, - } -) - -func (x MediaItemComponent) Enum() *MediaItemComponent { - p := new(MediaItemComponent) - *p = x - return p -} - -func (x MediaItemComponent) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (MediaItemComponent) Descriptor() protoreflect.EnumDescriptor { - return file_worker_proto_enumTypes[0].Descriptor() -} - -func (MediaItemComponent) Type() protoreflect.EnumType { - return &file_worker_proto_enumTypes[0] -} - -func (x MediaItemComponent) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use MediaItemComponent.Descriptor instead. -func (MediaItemComponent) EnumDescriptor() ([]byte, []int) { - return file_worker_proto_rawDescGZIP(), []int{0} -} - -type MediaItemProcessRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - FilePath string `protobuf:"bytes,3,opt,name=filePath,proto3" json:"filePath,omitempty"` - Components []MediaItemComponent `protobuf:"varint,4,rep,packed,name=components,proto3,enum=MediaItemComponent" json:"components,omitempty"` - Payload map[string]string `protobuf:"bytes,5,rep,name=payload,proto3" json:"payload,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MediaItemProcessRequest) Reset() { - *x = MediaItemProcessRequest{} - mi := &file_worker_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MediaItemProcessRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MediaItemProcessRequest) ProtoMessage() {} - -func (x *MediaItemProcessRequest) ProtoReflect() protoreflect.Message { - mi := &file_worker_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MediaItemProcessRequest.ProtoReflect.Descriptor instead. -func (*MediaItemProcessRequest) Descriptor() ([]byte, []int) { - return file_worker_proto_rawDescGZIP(), []int{0} -} - -func (x *MediaItemProcessRequest) GetUserId() string { - if x != nil { - return x.UserId - } - return "" -} - -func (x *MediaItemProcessRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *MediaItemProcessRequest) GetFilePath() string { - if x != nil { - return x.FilePath - } - return "" -} - -func (x *MediaItemProcessRequest) GetComponents() []MediaItemComponent { - if x != nil { - return x.Components - } - return nil -} - -func (x *MediaItemProcessRequest) GetPayload() map[string]string { - if x != nil { - return x.Payload - } - return nil -} - -type MediaItemProcessResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *MediaItemProcessResponse) Reset() { - *x = MediaItemProcessResponse{} - mi := &file_worker_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *MediaItemProcessResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MediaItemProcessResponse) ProtoMessage() {} - -func (x *MediaItemProcessResponse) ProtoReflect() protoreflect.Message { - mi := &file_worker_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MediaItemProcessResponse.ProtoReflect.Descriptor instead. -func (*MediaItemProcessResponse) Descriptor() ([]byte, []int) { - return file_worker_proto_rawDescGZIP(), []int{1} -} - -func (x *MediaItemProcessResponse) GetOk() bool { - if x != nil { - return x.Ok - } - return false -} - type GenerateEmbeddingRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` @@ -211,7 +30,7 @@ type GenerateEmbeddingRequest struct { func (x *GenerateEmbeddingRequest) Reset() { *x = GenerateEmbeddingRequest{} - mi := &file_worker_proto_msgTypes[2] + mi := &file_worker_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -223,7 +42,7 @@ func (x *GenerateEmbeddingRequest) String() string { func (*GenerateEmbeddingRequest) ProtoMessage() {} func (x *GenerateEmbeddingRequest) ProtoReflect() protoreflect.Message { - mi := &file_worker_proto_msgTypes[2] + mi := &file_worker_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -236,7 +55,7 @@ func (x *GenerateEmbeddingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateEmbeddingRequest.ProtoReflect.Descriptor instead. func (*GenerateEmbeddingRequest) Descriptor() ([]byte, []int) { - return file_worker_proto_rawDescGZIP(), []int{2} + return file_worker_proto_rawDescGZIP(), []int{0} } func (x *GenerateEmbeddingRequest) GetText() string { @@ -255,7 +74,7 @@ type GenerateEmbeddingResponse struct { func (x *GenerateEmbeddingResponse) Reset() { *x = GenerateEmbeddingResponse{} - mi := &file_worker_proto_msgTypes[3] + mi := &file_worker_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -267,7 +86,7 @@ func (x *GenerateEmbeddingResponse) String() string { func (*GenerateEmbeddingResponse) ProtoMessage() {} func (x *GenerateEmbeddingResponse) ProtoReflect() protoreflect.Message { - mi := &file_worker_proto_msgTypes[3] + mi := &file_worker_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -280,7 +99,7 @@ func (x *GenerateEmbeddingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateEmbeddingResponse.ProtoReflect.Descriptor instead. func (*GenerateEmbeddingResponse) Descriptor() ([]byte, []int) { - return file_worker_proto_rawDescGZIP(), []int{3} + return file_worker_proto_rawDescGZIP(), []int{1} } func (x *GenerateEmbeddingResponse) GetEmbedding() []float32 { @@ -292,55 +111,15 @@ func (x *GenerateEmbeddingResponse) GetEmbedding() []float32 { var File_worker_proto protoreflect.FileDescriptor -var file_worker_proto_rawDesc = string([]byte{ - 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8f, - 0x02, 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x3f, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, - 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x3a, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0x2a, 0x0a, 0x18, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x2e, 0x0a, 0x18, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x39, 0x0a, 0x19, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6d, 0x62, - 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x02, 0x52, 0x09, 0x65, 0x6d, - 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2a, 0x79, 0x0a, 0x12, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x0a, - 0x08, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x50, - 0x52, 0x45, 0x56, 0x49, 0x45, 0x57, 0x5f, 0x54, 0x48, 0x55, 0x4d, 0x42, 0x4e, 0x41, 0x49, 0x4c, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x53, 0x10, 0x02, 0x12, 0x12, - 0x0a, 0x0e, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, - 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x43, 0x45, 0x53, 0x10, 0x04, 0x12, 0x07, 0x0a, - 0x03, 0x4f, 0x43, 0x52, 0x10, 0x05, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, - 0x10, 0x06, 0x32, 0xa1, 0x01, 0x0a, 0x06, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x49, 0x0a, - 0x10, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, - 0x73, 0x12, 0x18, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, - 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x4d, 0x65, - 0x64, 0x69, 0x61, 0x49, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x19, 0x2e, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) +const file_worker_proto_rawDesc = "" + + "\n" + + "\fworker.proto\".\n" + + "\x18GenerateEmbeddingRequest\x12\x12\n" + + "\x04text\x18\x01 \x01(\tR\x04text\"9\n" + + "\x19GenerateEmbeddingResponse\x12\x1c\n" + + "\tembedding\x18\x01 \x03(\x02R\tembedding2V\n" + + "\x06Worker\x12L\n" + + "\x11GenerateEmbedding\x12\x19.GenerateEmbeddingRequest\x1a\x1a.GenerateEmbeddingResponse\"\x00B\tZ\aworker/b\x06proto3" var ( file_worker_proto_rawDescOnce sync.Once @@ -354,28 +133,19 @@ func file_worker_proto_rawDescGZIP() []byte { return file_worker_proto_rawDescData } -var file_worker_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_worker_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_worker_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_worker_proto_goTypes = []any{ - (MediaItemComponent)(0), // 0: MediaItemComponent - (*MediaItemProcessRequest)(nil), // 1: MediaItemProcessRequest - (*MediaItemProcessResponse)(nil), // 2: MediaItemProcessResponse - (*GenerateEmbeddingRequest)(nil), // 3: GenerateEmbeddingRequest - (*GenerateEmbeddingResponse)(nil), // 4: GenerateEmbeddingResponse - nil, // 5: MediaItemProcessRequest.PayloadEntry + (*GenerateEmbeddingRequest)(nil), // 0: GenerateEmbeddingRequest + (*GenerateEmbeddingResponse)(nil), // 1: GenerateEmbeddingResponse } var file_worker_proto_depIdxs = []int32{ - 0, // 0: MediaItemProcessRequest.components:type_name -> MediaItemComponent - 5, // 1: MediaItemProcessRequest.payload:type_name -> MediaItemProcessRequest.PayloadEntry - 1, // 2: Worker.MediaItemProcess:input_type -> MediaItemProcessRequest - 3, // 3: Worker.GenerateEmbedding:input_type -> GenerateEmbeddingRequest - 2, // 4: Worker.MediaItemProcess:output_type -> MediaItemProcessResponse - 4, // 5: Worker.GenerateEmbedding:output_type -> GenerateEmbeddingResponse - 4, // [4:6] is the sub-list for method output_type - 2, // [2:4] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 0, // 0: Worker.GenerateEmbedding:input_type -> GenerateEmbeddingRequest + 1, // 1: Worker.GenerateEmbedding:output_type -> GenerateEmbeddingResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_worker_proto_init() } @@ -388,14 +158,13 @@ func file_worker_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_worker_proto_rawDesc), len(file_worker_proto_rawDesc)), - NumEnums: 1, - NumMessages: 5, + NumEnums: 0, + NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_worker_proto_goTypes, DependencyIndexes: file_worker_proto_depIdxs, - EnumInfos: file_worker_proto_enumTypes, MessageInfos: file_worker_proto_msgTypes, }.Build() File_worker_proto = out.File diff --git a/api/pkg/services/worker/worker_grpc.pb.go b/api/pkg/services/worker/worker_grpc.pb.go index 8a5e390f..12457c97 100644 --- a/api/pkg/services/worker/worker_grpc.pb.go +++ b/api/pkg/services/worker/worker_grpc.pb.go @@ -19,7 +19,6 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Worker_MediaItemProcess_FullMethodName = "/Worker/MediaItemProcess" Worker_GenerateEmbedding_FullMethodName = "/Worker/GenerateEmbedding" ) @@ -27,7 +26,6 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type WorkerClient interface { - MediaItemProcess(ctx context.Context, in *MediaItemProcessRequest, opts ...grpc.CallOption) (*MediaItemProcessResponse, error) GenerateEmbedding(ctx context.Context, in *GenerateEmbeddingRequest, opts ...grpc.CallOption) (*GenerateEmbeddingResponse, error) } @@ -39,16 +37,6 @@ func NewWorkerClient(cc grpc.ClientConnInterface) WorkerClient { return &workerClient{cc} } -func (c *workerClient) MediaItemProcess(ctx context.Context, in *MediaItemProcessRequest, opts ...grpc.CallOption) (*MediaItemProcessResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(MediaItemProcessResponse) - err := c.cc.Invoke(ctx, Worker_MediaItemProcess_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *workerClient) GenerateEmbedding(ctx context.Context, in *GenerateEmbeddingRequest, opts ...grpc.CallOption) (*GenerateEmbeddingResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GenerateEmbeddingResponse) @@ -63,7 +51,6 @@ func (c *workerClient) GenerateEmbedding(ctx context.Context, in *GenerateEmbedd // All implementations must embed UnimplementedWorkerServer // for forward compatibility. type WorkerServer interface { - MediaItemProcess(context.Context, *MediaItemProcessRequest) (*MediaItemProcessResponse, error) GenerateEmbedding(context.Context, *GenerateEmbeddingRequest) (*GenerateEmbeddingResponse, error) mustEmbedUnimplementedWorkerServer() } @@ -75,9 +62,6 @@ type WorkerServer interface { // pointer dereference when methods are called. type UnimplementedWorkerServer struct{} -func (UnimplementedWorkerServer) MediaItemProcess(context.Context, *MediaItemProcessRequest) (*MediaItemProcessResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method MediaItemProcess not implemented") -} func (UnimplementedWorkerServer) GenerateEmbedding(context.Context, *GenerateEmbeddingRequest) (*GenerateEmbeddingResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GenerateEmbedding not implemented") } @@ -102,24 +86,6 @@ func RegisterWorkerServer(s grpc.ServiceRegistrar, srv WorkerServer) { s.RegisterService(&Worker_ServiceDesc, srv) } -func _Worker_MediaItemProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MediaItemProcessRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WorkerServer).MediaItemProcess(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Worker_MediaItemProcess_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WorkerServer).MediaItemProcess(ctx, req.(*MediaItemProcessRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Worker_GenerateEmbedding_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GenerateEmbeddingRequest) if err := dec(in); err != nil { @@ -145,10 +111,6 @@ var Worker_ServiceDesc = grpc.ServiceDesc{ ServiceName: "Worker", HandlerType: (*WorkerServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "MediaItemProcess", - Handler: _Worker_MediaItemProcess_Handler, - }, { MethodName: "GenerateEmbedding", Handler: _Worker_GenerateEmbedding_Handler, diff --git a/api/pkg/storage/disk.go b/api/pkg/storage/disk.go index 2572a900..3f38ab24 100644 --- a/api/pkg/storage/disk.go +++ b/api/pkg/storage/disk.go @@ -31,6 +31,7 @@ func (d *Disk) Upload(filePath, fileType, fileID string) (string, error) { if err != nil { return "", fmt.Errorf("error uploading file to disk as cannot copy contents: %w", err) } + return result, nil } @@ -50,6 +51,7 @@ func (d *Disk) Download(filePath, fileType, fileID string) error { if err != nil { return fmt.Errorf("error downloading file to disk as cannot copy contents: %w", err) } + return nil } @@ -58,6 +60,7 @@ func (d *Disk) Delete(fileType, fileID string) error { if err != nil { return fmt.Errorf("error deleting file from disk: %w", err) } + return nil } diff --git a/api/pkg/storage/disk_test.go b/api/pkg/storage/disk_test.go index 94e15e22..b9a517c1 100644 --- a/api/pkg/storage/disk_test.go +++ b/api/pkg/storage/disk_test.go @@ -23,37 +23,25 @@ func TestDiskUpload(t *testing.T) { ErrContains string }{ { - "error due to cannot open file", - &Config{Provider: "disk", Root: os.TempDir()}, - func() (string, func()) { + "error due to cannot open file", &Config{Provider: "disk", Root: os.TempDir()}, func() (string, func()) { return "", func() {} - }, - true, - "error uploading file to disk as cannot open file", + }, true, "error uploading file to disk as cannot open file", }, { - "error due to cannot create file", - &Config{Provider: "disk", Root: "invalid"}, - func() (string, func()) { + "error due to cannot create file", &Config{Provider: "disk", Root: "invalid"}, func() (string, func()) { file, _ := os.CreateTemp(os.TempDir(), "file") return file.Name(), func() { os.Remove(file.Name()) } - }, - true, - "error uploading file to disk as cannot create file", + }, true, "error uploading file to disk as cannot create file", }, { - "success", - &Config{Provider: "disk", Root: os.TempDir()}, - func() (string, func()) { + "success", &Config{Provider: "disk", Root: os.TempDir()}, func() (string, func()) { file, _ := os.CreateTemp(os.TempDir(), "file") return file.Name(), func() { os.Remove(file.Name()) } - }, - false, - "", + }, false, "", }, } for _, test := range tests { @@ -83,40 +71,25 @@ func TestDiskDownload(t *testing.T) { ErrContains string }{ { - "error due to cannot open file", - &Config{Provider: "disk", Root: os.TempDir()}, - func() (string, func()) { + "error due to cannot open file", &Config{Provider: "disk", Root: os.TempDir()}, func() (string, func()) { return "", func() {} - }, - "", - true, - "error downloading file to disk as cannot open file", + }, "", true, "error downloading file to disk as cannot open file", }, { - "error due to cannot create file", - &Config{Provider: "disk", Root: os.TempDir()}, - func() (string, func()) { + "error due to cannot create file", &Config{Provider: "disk", Root: os.TempDir()}, func() (string, func()) { file, _ := os.CreateTemp(os.TempDir()+"/originals", "file") return file.Name(), func() { os.Remove(file.Name()) } - }, - "invalid/invalid", - true, - "error downloading file to disk as cannot create file", + }, "invalid/invalid", true, "error downloading file to disk as cannot create file", }, { - "success", - &Config{Provider: "disk", Root: os.TempDir()}, - func() (string, func()) { + "success", &Config{Provider: "disk", Root: os.TempDir()}, func() (string, func()) { file, _ := os.CreateTemp(os.TempDir()+"/originals", "file") return file.Name(), func() { os.Remove(file.Name()) } - }, - "file", - false, - "", + }, "file", false, "", }, } for _, test := range tests { @@ -149,28 +122,20 @@ func TestDiskDelete(t *testing.T) { ErrContains string }{ { - "error", - &Config{Provider: "disk", Root: "invalid"}, - func() func() { + "error", &Config{Provider: "disk", Root: "invalid"}, func() func() { file, _ := os.CreateTemp(os.TempDir(), "file") return func() { os.Remove(file.Name()) } - }, - true, - "error deleting file from disk", + }, true, "error deleting file from disk", }, { - "success", - &Config{Provider: "disk", Root: os.TempDir()}, - func() func() { + "success", &Config{Provider: "disk", Root: os.TempDir()}, func() func() { file, _ := os.CreateTemp(os.TempDir(), "file") return func() { os.Remove(file.Name()) } - }, - false, - "", + }, false, "", }, } for _, test := range tests { diff --git a/api/pkg/storage/minio.go b/api/pkg/storage/minio.go index 188ffc80..be267c47 100644 --- a/api/pkg/storage/minio.go +++ b/api/pkg/storage/minio.go @@ -11,8 +11,7 @@ import ( const expiryTime = 24 -type ( - // Minio ... +type ( // Minio ... Minio struct { Client minioClient } @@ -31,13 +30,12 @@ func (m *Minio) Type() string { func (m *Minio) Upload(filePath, fileType, fileID string) (string, error) { contentType := "application/octet-stream" - objectOptions := minio.PutObjectOptions{ - ContentType: contentType, - } + objectOptions := minio.PutObjectOptions{ContentType: contentType} _, err := m.Client.FPutObject(context.Background(), fileType, fileID, filePath, objectOptions) if err != nil { return "", fmt.Errorf("error uploading file to minio: %w", err) } + return fmt.Sprintf("/%s/%s", fileType, fileID), nil } @@ -46,6 +44,7 @@ func (m *Minio) Download(filePath, fileType, fileID string) error { if err != nil { return fmt.Errorf("error downloading file from minio: %w", err) } + return nil } @@ -54,14 +53,15 @@ func (m *Minio) Delete(fileType, fileID string) error { if err != nil { return fmt.Errorf("error deleting file from minio: %w", err) } + return nil } func (m *Minio) Get(fileType, fileID string) (string, error) { - presignedURL, err := m.Client.PresignedGetObject(context.Background(), - fileType, fileID, expiryTime*time.Hour, url.Values{}) + presignedURL, err := m.Client.PresignedGetObject(context.Background(), fileType, fileID, expiryTime*time.Hour, url.Values{}) if err != nil { return "", fmt.Errorf("error getting file from minio: %w", err) } + return presignedURL.String(), nil } diff --git a/api/pkg/storage/minio_test.go b/api/pkg/storage/minio_test.go index 15f2a903..85fd040d 100644 --- a/api/pkg/storage/minio_test.go +++ b/api/pkg/storage/minio_test.go @@ -56,14 +56,10 @@ func TestMinioUpload(t *testing.T) { ErrContains string }{ { - "error", - true, - "error uploading file to minio", + "error", true, "error uploading file to minio", }, { - "success", - false, - "", + "success", false, "", }, } for _, test := range tests { @@ -88,14 +84,10 @@ func TestMinioDownload(t *testing.T) { ErrContains string }{ { - "error", - true, - "error downloading file from minio", + "error", true, "error downloading file from minio", }, { - "success", - false, - "", + "success", false, "", }, } for _, test := range tests { @@ -119,14 +111,10 @@ func TestMinioDelete(t *testing.T) { ErrContains string }{ { - "error", - true, - "error deleting file from minio", + "error", true, "error deleting file from minio", }, { - "success", - false, - "", + "success", false, "", }, } for _, test := range tests { @@ -150,14 +138,10 @@ func TestMinioGet(t *testing.T) { ErrContains string }{ { - "error", - true, - "error getting file from minio", + "error", true, "error getting file from minio", }, { - "success", - false, - "", + "success", false, "", }, } for _, test := range tests { diff --git a/api/pkg/storage/storage.go b/api/pkg/storage/storage.go index 88ba351e..5e3d46cd 100644 --- a/api/pkg/storage/storage.go +++ b/api/pkg/storage/storage.go @@ -2,11 +2,11 @@ package storage import ( "errors" + "log/slog" "os" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" - "golang.org/x/exp/slog" ) const ( @@ -18,8 +18,7 @@ const ( filePermission = 0o644 ) -type ( - // Provider ... +type ( // Provider ... Provider interface { Type() string Upload(filePath string, fileType string, fileID string) (string, error) @@ -38,18 +37,17 @@ type ( } ) -func Init(cfg *Config) Provider { //nolint: ireturn +//nolint:ireturn +func Init(cfg *Config) Provider { if cfg.Provider == ProviderMinio { minioClient, err := minio.New(cfg.Endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), - Secure: false, + Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""), Secure: false, }) if err != nil { slog.Error("error creating storage client", "error", err) } - return &Minio{ - Client: minioClient, - } + + return &Minio{Client: minioClient} } err := os.Mkdir(cfg.Root+"/originals", dirPermission) if err != nil && !errors.Is(err, os.ErrExist) { @@ -63,5 +61,6 @@ func Init(cfg *Config) Provider { //nolint: ireturn if err != nil && !errors.Is(err, os.ErrExist) { slog.Error("error creating storage thumbnails directory", "error", err) } + return &Disk{Root: cfg.Root} } diff --git a/docker-compose.yaml b/docker-compose.yaml index 31949a78..fb1e127b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,8 +5,8 @@ services: build: api restart: always ports: - - '5001:5001' - - '15001:15001' + - "5001:5001" + - "15001:15001" depends_on: - database - cache @@ -20,32 +20,20 @@ services: SMRITI_STORAGE_SECRET_KEY: smritipass volumes: - ./storage:/storage:rw - # deploy: - # resources: - # limits: - # cpus: '0.2' - # memory: 256M worker: container_name: worker build: worker restart: always ports: - - '5002:5002' - - '15002:15002' + - "5002:5002" + - "15002:15002" depends_on: - api environment: SMRITI_API_HOST: api - PYTHONUNBUFFERED: '1' - TOKENIZERS_PARALLELISM: false volumes: - ./storage:/storage:rw - ./models:/models:rw - # deploy: - # resources: - # limits: - # cpus: '1.2' - # memory: 3G # infra services database: container_name: database @@ -56,12 +44,7 @@ services: POSTGRES_PASSWORD: smritipass POSTGRES_DB: smriti ports: - - '5432:5432' - # deploy: - # resources: - # limits: - # cpus: '0.2' - # memory: 256M + - "5432:5432" cache: container_name: cache image: redis:8.0-M03-alpine @@ -69,26 +52,16 @@ services: command: > --requirepass smritipass ports: - - '6379:6379' - # deploy: - # resources: - # limits: - # cpus: '0.2' - # memory: 256M + - "6379:6379" storage: container_name: storage image: minio/minio:RELEASE.2025-02-28T09-55-16Z volumes: - ./storage:/storage ports: - - '9000:9000' + - "9000:9000" environment: MINIO_ROOT_USER: smritiuser MINIO_ROOT_PASSWORD: smritipass command: server --address 0.0.0.0:9000 /storage restart: always - # deploy: - # resources: - # limits: - # cpus: '0.2' - # memory: 256M \ No newline at end of file diff --git a/docs/docs/dev-guide/contribution.md b/docs/docs/dev-guide/contribution.md index 8cf95d93..b5381f48 100644 --- a/docs/docs/dev-guide/contribution.md +++ b/docs/docs/dev-guide/contribution.md @@ -1,7 +1,9 @@ # Contributing Guide ## Development + Here are some best practices we expect developers to follow: + - Write code that is easy to read and modify. - Use consistent coding standards for naming and formatting. - Break up large change requests into smaller, manageable ones. @@ -10,6 +12,7 @@ Here are some best practices we expect developers to follow: - Continuously improve the project with new features and bug fixes. ## Pull Request Checklist + - Followed the style guidelines of this project - Performed self-review of my code - Commented the code, particularly in hard-to-understand areas @@ -24,45 +27,61 @@ Here are some best practices we expect developers to follow: ### Unit Testing #### API + - Run unit tests + ``` make test ``` + - See code coverage + ``` make cover ``` -- Adding new unit tests + +- Adding new unit tests - New files/functions should have test cases in their respective `*_test.go` file in the same package. - Tests should have both positive and negative cases with mocking if required. - Check `api/internal/handlers/albums_test.go` as reference for adding unit tests. #### Worker + - Run unit tests + ``` make test ``` + - See code coverage + ``` make cover ``` -- Adding new unit tests - - New files/functions should have test cases in their respective `test_*.py` file in the `worker/tests` directory. + +- Adding new unit tests + - New files/functions should have test cases in their respective `test_*.py` file in the `pyworker/tests` directory. - Tests should have both positive and negative cases with mocking if required. - - Check `worker/tests/components/test_place.py` as reference for adding unit tests. + - Check `pyworker/tests/components/test_place.py` as reference for adding unit tests. ### Integration Testing #### Setup + - From the root directory of the project, run all services: + ``` make start ``` + - From `tests` folder, run following command to setup the test suite: + ``` make setup ``` + - From `tests` folder, run the command for invoking integration tests for all features: + ``` make test-all -``` \ No newline at end of file +``` diff --git a/docs/docs/dev-guide/design-document.md b/docs/docs/dev-guide/design-document.md index 93feed46..e50c6012 100644 --- a/docs/docs/dev-guide/design-document.md +++ b/docs/docs/dev-guide/design-document.md @@ -5,16 +5,18 @@ ### Components #### API + - Service written in Golang - REST API: [echo](https://echo.labstack.com/) - RPC: [gRPC + protobuf](https://grpc.io/) - - Postgres: [gorm](https://gorm.io/) + - Postgres: [pgx](https://github.com/jackc/pgx) - Linting: [golangci-lint](https://golangci-lint.run/) - Will read/write to Database - Will read/write to Disk - Will exchange protobuf with Worker: [api.proto](https://github.com/prabhuomkar/smriti/blob/master/protos/api.proto) #### Database + - [Schema](https://github.com/prabhuomkar/smriti/blob/master/infra/database/schema.sql) - Total number of tables: 10 - **Entities**: @@ -24,6 +26,7 @@ - User Management: `users` #### Worker + - Service written in Python - RPC: [gRPC + protobuf](https://grpc.io/) - Linting: [pylint](https://pypi.org/project/pylint/) @@ -33,50 +36,55 @@ ### Image & Video Processing -#### Parsing Metadata +#### Parsing Metadata + - [ExifTool](https://www.exiftool.org/) - Getting EXIF and XMP data #### Extracting Thumbnail + - [LibRaw](https://www.libraw.org/) - Processing and extracting RAW images - [ImageMagick](https://imagemagick.org/index.php) - General purpose extraction #### Supported File Formats -| Type | Extension | Support | -| ---- | --------- | ------- | -| Photo | .BMP | ✅ | -| Photo | .GIF | ✅ | -| Photo | .HEIC | ✅ | -| Photo | .ICO | ✅ | -| Photo | .JPG | ✅ | -| Photo | .PNG | ✅ | -| Photo | .TIFF | ✅ | -| Photo | .WEBP | ✅ | -| Photo | [RAW Formats](https://raw.pixls.us/) | ✅ | + +| Type | Extension | Support | +| ----- | ------------------------------------ | ------- | +| Photo | .BMP | ✅ | +| Photo | .GIF | ✅ | +| Photo | .HEIC | ✅ | +| Photo | .ICO | ✅ | +| Photo | .JPG | ✅ | +| Photo | .PNG | ✅ | +| Photo | .TIFF | ✅ | +| Photo | .WEBP | ✅ | +| Photo | [RAW Formats](https://raw.pixls.us/) | ✅ | **Post Stable Release, Scope for Video**: -| Type | Extension | Support | -| ---- | --------- | ------- | -| Video | 3GP | ❓ | -| Video | 3G2 | ❓ | -| Video | ASF | ❓ | -| Video | AVI | ✅ | -| Video | DIVX | ❓ | -| Video | M2T | ❓ | -| Video | M2TS | ❓ | -| Video | M4V | ❓ | -| Video | MKV | ❓ | -| Video | MMV | ❓ | -| Video | MOD | ❓ | -| Video | MOV | ✅ | -| Video | MP4 | ✅ | -| Video | MPG | ❓ | -| Video | MTS | ❓ | -| Video | TOD | ❓ | -| Video | WMV | ❓ | +| Type | Extension | Support | +| ----- | --------- | ------- | +| Video | 3GP | ❓ | +| Video | 3G2 | ❓ | +| Video | ASF | ❓ | +| Video | AVI | ✅ | +| Video | DIVX | ❓ | +| Video | M2T | ❓ | +| Video | M2TS | ❓ | +| Video | M4V | ❓ | +| Video | MKV | ❓ | +| Video | MMV | ❓ | +| Video | MOD | ❓ | +| Video | MOV | ✅ | +| Video | MP4 | ✅ | +| Video | MPG | ❓ | +| Video | MTS | ❓ | +| Video | TOD | ❓ | +| Video | WMV | ❓ | ### File Storage & Retrieval + - Support for several file storage systems behind a common interface: + ``` interface { upload() // upload the file (in chunks if required) @@ -84,6 +92,7 @@ interface { get() // get the file } ``` + - Out of the box incremental support for storage: - Disk - [MinIO](https://min.io/) @@ -91,23 +100,25 @@ interface { - Best practices for security and other similar aspects for connecting to storage will be decided later ### Machine Learning Inference + - Toggles for using CPU and GPU for inference - Worker configuration sent to worker on startup request: + ```yaml - name: places source: openstreetmap # google-maps, geojson, paid services, etc. - name: classification source: convnext - params: + params: - threshold source: yolo - name: ocr source: paddlepaddle - params: + params: - threshold - name: faces source: tbd - params: + params: - face_size - threshold - name: speech @@ -115,6 +126,7 @@ interface { params: - tbd ``` + - Default Models: - Classification - [EfficientNet](https://github.com/pytorch/vision/blob/main/torchvision/models/efficientnet.py) - OCR - [PaddleOCR Models](https://github.com/PaddlePaddle/PaddleOCR) @@ -123,17 +135,21 @@ interface { - Face Detection - [TBD](https://github.com) ## Performance + - Benchmarking with several parallel uploads and system configuration - Results of benchmarks and some graphs ## Testing + - E2E Automation Testing using `behave` ## Security + - Authentication mechanisms; Basic Auth & JWT - Accessing CDN files using hash keys ## Deployment -- Docker Deployment + +- Docker Deployment - Volumes and Other Concerns -- Working with HTTPS \ No newline at end of file +- Working with HTTPS diff --git a/docs/docs/dev-guide/environment.md b/docs/docs/dev-guide/environment.md index 80d217e5..eaf0b045 100644 --- a/docs/docs/dev-guide/environment.md +++ b/docs/docs/dev-guide/environment.md @@ -1,14 +1,20 @@ # Environment Setup + The following guide will help you in setting up the development environment to work on Smriti. ## Prerequisites -Make sure, before you start any development, following things are installed and available on your system of choice: + +Make sure, before you start any development, following things are installed and available on your system of choice: + - [Git](https://git-scm.com/) - [Docker](https://www.docker.com/) +- [Protocol Buffers](https://protobuf.dev/installation/) - [Common Sense](https://en.wikipedia.org/wiki/Common_sense) ## Getting Codebase -Git clone the repository using: + +Git clone the repository using: + ``` git clone git@github.com:prabhuomkar/smriti.git ``` @@ -16,31 +22,50 @@ git clone git@github.com:prabhuomkar/smriti.git ## Setup ### API -- Install [Golang 1.24](https://go.dev/dl/) or above -- Install [golangci-lint 1.64.6](https://golangci-lint.run/) or above + +- Install [Golang 1.25](https://go.dev/dl/) or above +- Install [golangci-lint 2.4.0](https://golangci-lint.run/) or above - Run the following command to finish your API setup + ``` make setup-api ``` ### Worker + +- Install [CMake 3.30](https://cmake.org/download/) or above +- Install [cpplint 2.0.0](https://pypi.org/project/cpplint/) or above +- Install [clang-tidy 19.1.7](https://clang.llvm.org/extra/clang-tidy/) or above +- Install [clang-format 19.1.7](https://clang.llvm.org/docs/ClangFormat.html) or above + +``` +make setup-worker +``` + +### PyWorker + - Install [Python 3.12](https://www.python.org/downloads/) or above - Install [pylint 3.3.4](https://pypi.org/project/pylint/) or above - Run the following command to finish your Worker setup + ``` -make setup-worker +make setup-pyworker ``` ### Docs + - Install [Node 18.10](https://nodejs.org/en/download/) or above - Run the following command to finish your Docs setup + ``` make setup-docs ``` ### Tests + - Install [Python 3.12](https://www.python.org/downloads/) or above - Run the following command to finish your Tests setup + ``` make setup-tests -``` \ No newline at end of file +``` diff --git a/docs/docs/dev-guide/folder-structure.md b/docs/docs/dev-guide/folder-structure.md index 46108a89..bc9167da 100644 --- a/docs/docs/dev-guide/folder-structure.md +++ b/docs/docs/dev-guide/folder-structure.md @@ -1,5 +1,6 @@ # Folder Structure -- `/`: + +- `/`: - `Makefile`: Helpful commands for interacting with project - `docker-compose.yaml`: Containerized services for local integration tests - `api`: contains the source code for the API server @@ -9,4 +10,5 @@ - `scripts`: contains scripts for setting up test data, models for running locally, etc. - `protos`: contains Protocol Buffer definitions for API and Worker services - `tests`: contains code and test cases for Integration Tests -- `worker`: contains the source code for the Worker server with its individual components for Place Detection, Face Detection & Clustering and Things Detection +- `worker`: contains the source code for the Worker server with its individual + components for Place Detection, Face Detection & Clustering, Text Detection and Caption Generation diff --git a/docs/docs/dev-guide/introduction.md b/docs/docs/dev-guide/introduction.md index 5a05d8c7..0a19bf00 100644 --- a/docs/docs/dev-guide/introduction.md +++ b/docs/docs/dev-guide/introduction.md @@ -1,13 +1,15 @@ # Introduction + Welcome to the Smriti Developer Guide, your go-to resource for technical information about Smriti. Built on a robust and advanced platform, Smriti offers a seamless experience for users. -As a technical user, this guide provides information on various features of Smriti and how to implement them to optimize your photo and video management workflow. It covers everything from the system requirements for running Smriti to the technical specifications of our cloud storage solution. You can also find detailed instructions on how to migrate to Smriti from your existing solution, and learn how to use our APIs to build custom solutions. +As a technical user, this guide provides information on various features of Smriti and how to implement them to optimize your photo and video management workflow. It covers everything from the system requirements for running Smriti to the technical specifications of our cloud storage solution. You can also find detailed instructions on how to migrate to Smriti from your existing solution, and learn how to use our APIs to build custom solutions. ## Architecture Architecture ## Table of Contents + - [Environment Setup](environment.md) - [Folder Structure](folder-structure.md) - [Contribution Guide](contribution.md) diff --git a/docs/docs/dev-guide/notes.md b/docs/docs/dev-guide/notes.md index f2562902..a18027b3 100644 --- a/docs/docs/dev-guide/notes.md +++ b/docs/docs/dev-guide/notes.md @@ -1,4 +1,5 @@ # Developer Notes + - [x] ML models should be agnostic and configurable - [x] Each action should be configurable from the environment config e.g. Face grouping, Place Identification, File Size Limits - [x] API-first techniques and then build Web UI & Mobile Apps @@ -12,30 +13,31 @@ - [x] Try to achieve best ratings incrementally as on: https://github.com/meichthys/foss_photo_libraries ## Features -| Feature | Support | -| - | - | -| Demo | 🚧 | -| Freeness | ✅ | -| Automatic Mobile Upload | 🚧 | -| Web App | 🚧 | -| Android App | 🚧 | -| iOS App | 🚧 | -| Desktop App | 🚧 | -| LivePhotos | ✅ | -| Videos | ✅ | -| Geolocation | ✅ | -| Discovery | 🚧 | -| Existing Folders | 🚧 | -| Albums | ✅ | -| Slideshow | 🚧 | -| Timeline | 🚧 | -| Sharing | ✅ | -| Search | ✅ | -| Duplicate Handling | ✅ | -| User Defined Tags | 🚧 | -| Docker Installation | ✅ | -| Object/Face Recognition | ✅ | -| Basic Editing | 🚧 | -| EXIF Data | ✅ | -| Multiple User Support | ✅ | -| Authentication Providers | 🚧 | + +| Feature | Support | +| ------------------------ | ------- | +| Demo | 🚧 | +| Freeness | ✅ | +| Automatic Mobile Upload | 🚧 | +| Web App | 🚧 | +| Android App | 🚧 | +| iOS App | 🚧 | +| Desktop App | 🚧 | +| LivePhotos | ✅ | +| Videos | ✅ | +| Geolocation | ✅ | +| Discovery | 🚧 | +| Existing Folders | 🚧 | +| Albums | ✅ | +| Slideshow | 🚧 | +| Timeline | 🚧 | +| Sharing | ✅ | +| Search | ✅ | +| Duplicate Handling | ✅ | +| User Defined Tags | 🚧 | +| Docker Installation | ✅ | +| Object/Face Recognition | ✅ | +| Basic Editing | 🚧 | +| EXIF Data | ✅ | +| Multiple User Support | ✅ | +| Authentication Providers | 🚧 | diff --git a/docs/docs/dev-guide/roadmap.md b/docs/docs/dev-guide/roadmap.md index 5bcabba7..880bbf72 100644 --- a/docs/docs/dev-guide/roadmap.md +++ b/docs/docs/dev-guide/roadmap.md @@ -1,4 +1,5 @@ # Roadmap + - [x] Album Creation & Its CRUD (v2022.09.30) - [x] Library APIs with Favourites, Hidden & Trash (v2022.09.30) - [x] Get All Places from Photos & its Collection (v2022.10.31) @@ -15,5 +16,8 @@ - [x] Open up for contributions to build Mobile & Web Application (v2023.08.31) - [x] Enhance Image & Video Support (v2024.03.09) - [x] Improvements to E2E Test Suite (v2024.03.09) -- [ ] Sync content from Social Media Websites (v2024.07.31) -- [ ] Generation of Memories (v2024.07.31) +- [ ] Faster Worker using C++ (v2025.09.30) +- [ ] Improved Video Support C++ (v2025.10.20) +- [ ] UI Features except Home and Search (v2025.11.23) +- [ ] Sync content from other providers: Apple iCloud, Google Photos (v2026.01.31) +- [ ] Generation of Memories (v2025.03.31) diff --git a/docs/docs/user-guide/deployment.md b/docs/docs/user-guide/deployment.md index 0c8e08f8..f3286ae8 100644 --- a/docs/docs/user-guide/deployment.md +++ b/docs/docs/user-guide/deployment.md @@ -1,7 +1,9 @@ # Deployment ## Amazon Web Services (AWS) + Coming Soon! ## Oracle Cloud Infrastructure (OCI) -Coming Soon! \ No newline at end of file + +Coming Soon! diff --git a/docs/docs/user-guide/features/albums.md b/docs/docs/user-guide/features/albums.md index d4bb58c8..62f0fda9 100644 --- a/docs/docs/user-guide/features/albums.md +++ b/docs/docs/user-guide/features/albums.md @@ -1,5 +1,7 @@ # Albums + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_ALBUMS: true ``` @@ -7,16 +9,21 @@ SMRITI_FEATURE_ALBUMS: true ## Use Cases ### Create Album + TBD ### Update Album + TBD ### Get All Albums + TBD ### Get Album + TBD ### Delete Album -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/features/explore.md b/docs/docs/user-guide/features/explore.md index 3058f5b4..028e1fde 100644 --- a/docs/docs/user-guide/features/explore.md +++ b/docs/docs/user-guide/features/explore.md @@ -1,5 +1,7 @@ # Explore + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_EXPLORE: true ``` @@ -7,4 +9,5 @@ SMRITI_FEATURE_EXPLORE: true ## Use Cases ### Get Years Today -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/features/library.md b/docs/docs/user-guide/features/library.md index 7299dabe..060da6e3 100644 --- a/docs/docs/user-guide/features/library.md +++ b/docs/docs/user-guide/features/library.md @@ -1,19 +1,25 @@ # Library ## Favourites + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_FAVOURITES: true ``` ## Hidden + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_HIDDEN: true ``` ## Trash + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_TRASH: true -``` \ No newline at end of file +``` diff --git a/docs/docs/user-guide/features/metadata.md b/docs/docs/user-guide/features/metadata.md index bf33ca07..54d0ccfb 100644 --- a/docs/docs/user-guide/features/metadata.md +++ b/docs/docs/user-guide/features/metadata.md @@ -1,5 +1,7 @@ # Metadata + This feature is enabled by default and can be configured like: + ```bash SMRITI_ML_METADATA_PARAMS: {"thumbnail_size":"512"} ``` diff --git a/docs/docs/user-guide/features/people.md b/docs/docs/user-guide/features/people.md index 4c7e00b1..cfc93ec1 100644 --- a/docs/docs/user-guide/features/people.md +++ b/docs/docs/user-guide/features/people.md @@ -1,5 +1,7 @@ # People + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_PEOPLE: true SMRITI_ML_FACES: true @@ -10,10 +12,13 @@ SMRITI_ML_FACES_PARAMS: {"minutes":"1","face_threshold":"0.9","model":"vggface2" ## Use Cases ### Get All People + TBD ### Get Person + TBD ### Get Person MediaItems -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/features/places.md b/docs/docs/user-guide/features/places.md index 165def9f..434ff402 100644 --- a/docs/docs/user-guide/features/places.md +++ b/docs/docs/user-guide/features/places.md @@ -1,5 +1,7 @@ # Places + Enable this feature using API Configuration: + ```bash SMRITI_FEATURE_PLACES: true SMRITI_ML_PLACES: true @@ -9,10 +11,13 @@ SMRITI_ML_PLACES_PROVIDER: openstreetmap ## Use Cases ### Get All Places + TBD ### Get Place + TBD ### Get Place MediaItems -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/features/search.md b/docs/docs/user-guide/features/search.md index e01e9db2..7567391a 100644 --- a/docs/docs/user-guide/features/search.md +++ b/docs/docs/user-guide/features/search.md @@ -1,5 +1,7 @@ # Search + Enable this feature using API Configuration: + ```bash SMRITI_ML_SEARCH: true SMRITI_ML_SEARCH_PROVIDER: pytorch @@ -9,4 +11,5 @@ SMRITI_ML_SEARCH_PARAMS: {"tokenizer_dir":"search_tokenizer","processor_dir":"se ## Use Cases ### Search MediaItems -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/features/things.md b/docs/docs/user-guide/features/things.md deleted file mode 100644 index 5b9c6f81..00000000 --- a/docs/docs/user-guide/features/things.md +++ /dev/null @@ -1,19 +0,0 @@ -# Things -Enable this feature using API Configuration: -```bash -SMRITI_FEATURE_THINGS: true -SMRITI_ML_CLASSIFICATION: true -SMRITI_ML_CLASSIFICATION_PROVIDER: pytorch -SMRITI_ML_CLASSIFICATION_PARAMS: {"file":"classification_v240624.pt"} -``` - -## Use Cases - -### Get All Things -TBD - -### Get Thing -TBD - -### Get Thing MediaItems -TBD \ No newline at end of file diff --git a/docs/docs/user-guide/features/users.md b/docs/docs/user-guide/features/users.md index 5ebae7ea..eff5fb24 100644 --- a/docs/docs/user-guide/features/users.md +++ b/docs/docs/user-guide/features/users.md @@ -3,16 +3,21 @@ ## Use Cases ### Create User + TBD ### Update User + TBD ### Get All Users + TBD ### Get User + TBD ### Delete User -TBD \ No newline at end of file + +TBD diff --git a/docs/docs/user-guide/installation.md b/docs/docs/user-guide/installation.md index 91f8656d..0f56d3be 100644 --- a/docs/docs/user-guide/installation.md +++ b/docs/docs/user-guide/installation.md @@ -1,17 +1,21 @@ # Installation ## Docker -- Deploying with Docker is plain and simple. -Refer to [docker-compose.yml](https://github.com/prabhuomkar/smriti/blob/master/docker-compose.yaml) for setting up services. + +- Deploying with Docker is plain and simple. + Refer to [docker-compose.yml](https://github.com/prabhuomkar/smriti/blob/master/docker-compose.yaml) for setting up services. - Run all the services using: + ```bah docker-compose up -d ``` ### Deploy using Docker Swarm -- [Docker Swarm](https://docs.docker.com/engine/swarm/) is the preferred approach for now to deploy Smriti. + +- [Docker Swarm](https://docs.docker.com/engine/swarm/) is the preferred approach for now to deploy Smriti. - Environment variables can be set in [infra/deployments/docker-swarm/docker-compose.yaml](https://github.com/prabhuomkar/smriti/blob/master/infra/deployments/docker-swarm/docker-compose.yaml). - Run following command to start services: + ```bash docker swarm init docker swarm join --token @@ -19,4 +23,5 @@ docker compose up -d ``` ## Kubernetes -Coming Soon! \ No newline at end of file + +Coming Soon! diff --git a/docs/docs/user-guide/introduction.md b/docs/docs/user-guide/introduction.md index 2b76e64d..727cc5f2 100644 --- a/docs/docs/user-guide/introduction.md +++ b/docs/docs/user-guide/introduction.md @@ -1,4 +1,9 @@ # Introduction -Welcome to the Smriti User Guide! This guide is designed to provide you with all the information you need to get started, from installation and setup to using the application's various features and functions. -This guide will provide you with step-by-step instructions on how to install and configure the application, as well as an overview of its key features and how to use them. Whether you are a developer looking to customize the application or a non-technical user simply looking to get started, this guide is designed to be accessible and easy to follow. \ No newline at end of file +Welcome to the Smriti User Guide! This guide is designed to provide you with all the information you need to get +started, from installation and setup to using the application's various features and functions. + +This guide will provide you with step-by-step instructions on how to install and configure the application, +as well as an overview of its key features and how to use them. Whether you are a developer looking to customize +the application or a non-technical user simply looking to get started, this guide is designed to be accessible and +easy to follow. diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 66926877..f3da6351 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -48,7 +48,7 @@ const config = { id: 'wip', content: 'Currently under active development, check out Contributing Guide', - backgroundColor: '#071320', + backgroundColor: '#9d8cfd', textColor: '#ffffff', isCloseable: false, }, diff --git a/docs/package-lock.json b/docs/package-lock.json index 14fce31f..25627392 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,58 +1,77 @@ { "name": "smriti", - "version": "24.06.24", + "version": "25.09.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "smriti", - "version": "24.06.24", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-google-gtag": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@mdx-js/react": "^3.0.1", + "version": "25.09.30", + "dependencies": { + "@babel/traverse": "^7.28.4", + "@docusaurus/core": "^3.6.0", + "@docusaurus/plugin-google-gtag": "^3.6.0", + "@docusaurus/preset-classic": "^3.6.0", + "@docusaurus/utils": "^3.6.0", + "@mdx-js/react": "^3.1.1", "loader-utils": "^3.3.1", - "prism-react-renderer": "^2.3.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "redocusaurus": "^2.0.2", - "semver": "^7.6.0" + "prism-react-renderer": "^2.4.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "redocusaurus": "^2.5.0", + "semver": "^7.7.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.0.1" + "@docusaurus/module-type-aliases": "^3.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.3.0.tgz", + "integrity": "sha512-KqPVLdVNfoJzX5BKNGM9bsW8saHeyax8kmPFXul5gejrSPN3qss7PgsFH5mMem7oR8tvjvNkia97ljEYPYCN8Q==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz", + "integrity": "sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.9", + "@algolia/autocomplete-shared": "1.17.9" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz", + "integrity": "sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.9" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz", + "integrity": "sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.9" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -60,199 +79,240 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz", + "integrity": "sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==", + "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", - "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "node_modules/@algolia/client-abtesting": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.37.0.tgz", + "integrity": "sha512-Dp2Zq+x9qQFnuiQhVe91EeaaPxWBhzwQ6QnznZQnH9C1/ei3dvtmAFfFeaTxM6FzfJXDLvVnaQagTYFTQz3R5g==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/cache-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", - "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", - "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "node_modules/@algolia/client-analytics": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.37.0.tgz", + "integrity": "sha512-wyXODDOluKogTuZxRII6mtqhAq4+qUR3zIUJEKTiHLe8HMZFxfUEI4NO2qSu04noXZHbv/sRVdQQqzKh12SZuQ==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-account": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", - "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", - "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/transporter": "4.23.3" + "node_modules/@algolia/client-common": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.37.0.tgz", + "integrity": "sha512-GylIFlPvLy9OMgFG8JkonIagv3zF+Dx3H401Uo2KpmfMVBBJiGfAb9oYfXtplpRMZnZPxF5FnkWaI/NpVJMC+g==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-analytics": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", - "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "node_modules/@algolia/client-insights": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.37.0.tgz", + "integrity": "sha512-T63afO2O69XHKw2+F7mfRoIbmXWGzgpZxgOFAdP3fR4laid7pWBt20P4eJ+Zn23wXS5kC9P2K7Bo3+rVjqnYiw==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", - "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "node_modules/@algolia/client-personalization": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.37.0.tgz", + "integrity": "sha512-1zOIXM98O9zD8bYDCJiUJRC/qNUydGHK/zRK+WbLXrW1SqLFRXECsKZa5KoG166+o5q5upk96qguOtE8FTXDWQ==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-personalization": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", - "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.37.0.tgz", + "integrity": "sha512-31Nr2xOLBCYVal+OMZn1rp1H4lPs1914Tfr3a34wU/nsWJ+TB3vWjfkUUuuYhWoWBEArwuRzt3YNLn0F/KRVkg==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", - "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.37.0.tgz", + "integrity": "sha512-DAFVUvEg+u7jUs6BZiVz9zdaUebYULPiQ4LM2R4n8Nujzyj7BZzGr2DCd85ip4p/cx7nAZWKM8pLcGtkTRTdsg==", + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/transporter": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" - }, - "node_modules/@algolia/logger-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", - "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==" + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" }, - "node_modules/@algolia/logger-console": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", - "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "node_modules/@algolia/ingestion": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.37.0.tgz", + "integrity": "sha512-pkCepBRRdcdd7dTLbFddnu886NyyxmhgqiRcHHaDunvX03Ij4WzvouWrQq7B7iYBjkMQrLS8wQqSP0REfA4W8g==", + "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/recommend": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", - "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "node_modules/@algolia/monitoring": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.37.0.tgz", + "integrity": "sha512-fNw7pVdyZAAQQCJf1cc/ih4fwrRdQSgKwgor4gchsI/Q/ss9inmC6bl/69jvoRSzgZS9BX4elwHKdo0EfTli3w==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", - "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "node_modules/@algolia/recommend": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.37.0.tgz", + "integrity": "sha512-U+FL5gzN2ldx3TYfQO5OAta2TBuIdabEdFwD5UVfWPsZE5nvOKkc/6BBqP54Z/adW/34c5ZrvvZhlhNTZujJXQ==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-common": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", - "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==" - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", - "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.37.0.tgz", + "integrity": "sha512-Ao8GZo8WgWFABrU7iq+JAftXV0t+UcOtCDL4mzHHZ+rQeTTf1TZssr4d0vIuoqkVNnKt9iyZ7T4lQff4ydcTrw==", + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/transporter": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", - "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "node_modules/@algolia/requester-fetch": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.37.0.tgz", + "integrity": "sha512-H7OJOXrFg5dLcGJ22uxx8eiFId0aB9b0UBhoOi4SMSuDBe6vjJJ/LeZyY25zPaSvkXNBN3vAM+ad6M0h6ha3AA==", + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/requester-common": "4.23.3" + "@algolia/client-common": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "node_modules/@algolia/requester-node-http": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.37.0.tgz", + "integrity": "sha512-npZ9aeag4SGTx677eqPL3rkSPlQrnzx/8wNrl1P7GpWq9w/eTmRbOq+wKrJ2r78idlY0MMgmY/mld2tq6dc44g==", + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@algolia/client-common": "5.37.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 14.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -271,55 +331,48 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", - "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", - "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -331,23 +384,23 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", - "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -361,17 +414,19 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", - "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -385,93 +440,71 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", - "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -481,32 +514,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", - "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-wrap-function": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -516,13 +552,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -531,109 +568,81 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", - "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "license": "MIT", "dependencies": { - "@babel/helper-function-name": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/types": "^7.28.4" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -642,12 +651,28 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", - "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -657,11 +682,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", - "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -671,13 +697,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", - "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -687,12 +714,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", - "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -705,42 +733,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -752,6 +745,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -759,23 +753,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -785,11 +769,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -798,128 +783,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", - "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -929,11 +799,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -946,6 +817,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -958,11 +830,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", - "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -972,14 +845,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", - "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -989,13 +862,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1005,11 +879,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", - "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1019,11 +894,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", - "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", + "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1033,12 +909,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", - "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1048,13 +925,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", - "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1064,18 +941,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1085,12 +961,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", - "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1100,11 +977,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1114,12 +993,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", - "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1129,11 +1009,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", - "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1142,13 +1023,45 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", - "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1158,12 +1071,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", - "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1173,12 +1086,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", - "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1188,12 +1101,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", - "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1203,13 +1117,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", - "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1219,12 +1134,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", - "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1234,11 +1149,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", - "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1248,12 +1164,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", - "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1263,11 +1179,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", - "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1277,12 +1194,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", - "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1292,13 +1210,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1308,14 +1226,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", - "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "license": "MIT", "dependencies": { - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1325,12 +1244,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", - "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1340,12 +1260,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", - "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1355,11 +1276,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", - "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1369,12 +1291,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", - "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1384,12 +1306,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", - "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1399,14 +1321,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", - "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.7" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1416,12 +1340,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", - "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1431,12 +1356,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", - "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1446,13 +1371,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", - "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1462,11 +1387,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1476,12 +1402,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", - "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1491,14 +1418,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", - "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1508,11 +1435,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", - "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1522,11 +1450,12 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", - "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1536,11 +1465,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", - "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1550,15 +1480,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", - "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1568,11 +1499,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", - "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" + "@babel/plugin-transform-react-jsx": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1582,12 +1514,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", - "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1597,12 +1530,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", - "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1611,12 +1544,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", - "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1626,15 +1576,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", - "babel-plugin-polyfill-regenerator": "^0.6.1", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "engines": { @@ -1648,16 +1599,18 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", - "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1667,12 +1620,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1682,11 +1636,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", - "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1696,11 +1651,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", - "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1710,11 +1666,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", - "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1724,14 +1681,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz", - "integrity": "sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-typescript": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1741,11 +1700,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", - "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1755,12 +1715,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", - "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1770,12 +1731,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", - "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1785,12 +1747,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", - "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1800,90 +1763,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", - "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", - "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.24.7", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.24.7", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.7", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.24.7", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.24.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-modules-systemjs": "^7.24.7", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.7", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1897,6 +1850,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -1905,6 +1859,7 @@ "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1915,16 +1870,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", - "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.24.7", - "@babel/plugin-transform-react-jsx-development": "^7.24.7", - "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1934,15 +1890,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", - "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-syntax-jsx": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.24.7" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1951,75 +1908,67 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.7.tgz", - "integrity": "sha512-eytSX6JLBY6PVAeQa2bFlDx/7Mmln/gaEpsit5a3WEvjGfiIytEsgAwuIXCPM0xvw0v0cJn3ilq0/TvXrW0kgA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "license": "MIT", "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" + "core-js-pure": "^3.43.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", - "debug": "^4.3.1", - "globals": "^11.1.0" + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2034,33 +1983,1223 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.0.tgz", + "integrity": "sha512-r2L8KNg5Wriq5n8IUQcjzy2Rh37J5YjzP9iOyHZL5fxdWYHB08vqykHQa4wAzN/tXwDuCHnhQDGCtxfS76xn7g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.11.tgz", + "integrity": "sha512-AtH22zLHTLm64HLdpv5EedT/zmYTm1MtdQbQhRZXxEB6iYtS6SrS1jLX3TcmUWMFzpumK/OVylCm3HcLms4slw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.0.tgz", + "integrity": "sha512-7q+OuUqfowRrP84m/Jl0wv3pfCQyUTCW5MxDIux+/yty5IkUUHOTigCjrC0Fjy3OT0ncGLudHbfLWmP7E1arNA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.11.tgz", + "integrity": "sha512-cQpXBelpTx0YhScZM5Ve0jDCA4RzwFc7oNafzZOGgCHt/GQVYiU8Vevz9QJcwy/W0Pyi/BneY+KMjz23lI9r+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.1.tgz", + "integrity": "sha512-c7hyBtbF+jlHIcUGVdWY06bHICgguV9ypfcELU3eU3W/9fiz2dxM8PqxQk2ndXYTzLnwPvNNqu1yCmQ++N6Dcg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.7.tgz", + "integrity": "sha512-cq/zWaEkpcg3RttJ5+GdNwk26NwxY5KgqgtNL777Fdd28AVGHxuBvqmK4Jq4oKhW1NX4M2LbgYAVVN0NZ+/XYQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.11.tgz", + "integrity": "sha512-8M3mcNTL3cGIJXDnvrJ2oWEcKi3zyw7NeYheFKePUlBmLYm1gkw9Rr/BA7lFONrOPeQA3yeMPldrrws6lqHrug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.11.tgz", + "integrity": "sha512-9meZbsVWTZkWsSBazQips3cHUOT29a/UAwFz0AMEXukvpIGGDR9+GMl3nIckWO5sPImsadu4F5Zy+zjt8QgCdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.3.tgz", + "integrity": "sha512-RtYYm2qUIu9vAaHB0cC8rQGlOCQAUgEc2tMr7ewlGXYipBQKjoWmyVArqsk7SEr8N3tErq6P6UOJT3amaVof5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.10.tgz", + "integrity": "sha512-g7Lwb294lSoNnyrwcqoooh9fTAp47rRNo+ILg7SLRSMU3K9ePIwRt566sNx+pehiCelv4E1ICaU1EwLQuyF2qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.11.tgz", + "integrity": "sha512-9f03ZGxZ2VmSCrM4SDXlAYP+Xpu4VFzemfQUQFL9OYxAbpvDy0FjDipZ0i8So1pgs8VIbQI0bNjFWgfdpGw8ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.0.tgz", + "integrity": "sha512-fWCXRasX17N1NCPTCuwC3FJDV+Wc031f16cFuuMEfIsYJ1q5ABCa59W0C6VeMGqjNv6ldf37vvwXXAeaZjD9PA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.11.tgz", + "integrity": "sha512-oQ5fZvkcBrWR+k6arHXk0F8FlkmD4IxM+rcGDLWrF2f31tWyEM3lSraeWAV0f7BGH6LIrqmyU3+Qo/1acfoJng==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.9.0.tgz", + "integrity": "sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==", + "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.9.0.tgz", + "integrity": "sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==", + "license": "MIT", "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", - "algoliasearch": "^4.19.1" + "@algolia/autocomplete-core": "1.17.9", + "@algolia/autocomplete-preset-algolia": "1.17.9", + "@docsearch/css": "3.9.0", + "algoliasearch": "^5.14.2" }, "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", "search-insights": ">= 1 < 3" }, "peerDependenciesMeta": { @@ -2078,79 +3217,123 @@ } } }, - "node_modules/@docusaurus/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", - "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "node_modules/@docusaurus/babel": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.8.1.tgz", + "integrity": "sha512-3brkJrml8vUbn9aeoZUlJfsI/GqyFcDgQJwQkmBtclJgWDEQBKKeagZfOgx0WfUQhagL1sQLNW0iBdxnI863Uw==", + "license": "MIT", "dependencies": { - "@babel/core": "^7.23.3", - "@babel/generator": "^7.23.3", + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.22.9", - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@babel/runtime": "^7.22.6", - "@babel/runtime-corejs3": "^7.22.6", - "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "autoprefixer": "^10.4.14", - "babel-loader": "^9.1.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.8.1", + "@docusaurus/utils": "3.8.1", "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.8.1.tgz", + "integrity": "sha512-/z4V0FRoQ0GuSLToNjOSGsk6m2lQUG4FRn8goOVoZSRsTrU8YR2aJacX5K3RG18EaX9b+52pN4m1sL3MQZVsQA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.8.1", + "@docusaurus/cssnano-preset": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.8.1.tgz", + "integrity": "sha512-ENB01IyQSqI2FLtOzqSI3qxG2B/jP4gQPahl2C3XReiLebcVh5B5cB9KYFvdoOqOWPyr5gXK4sjgTKv7peXCrA==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.8.1", + "@docusaurus/bundler": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "boxen": "^6.2.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "clean-css": "^5.3.2", "cli-table3": "^0.6.3", "combine-promises": "^1.1.0", "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", "core-js": "^3.31.1", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "del": "^6.1.1", "detect-port": "^1.5.1", "escape-html": "^1.0.3", "eta": "^2.2.0", "eval": "^0.1.8", - "file-loader": "^6.2.0", + "execa": "5.1.1", "fs-extra": "^11.1.1", - "html-minifier-terser": "^7.2.0", "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.5.3", + "html-webpack-plugin": "^5.6.0", "leven": "^3.1.0", "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.6", + "open": "^8.4.0", "p-map": "^4.0.0", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", "react-loadable-ssr-addon-v5-slorber": "^1.0.1", "react-router": "^5.3.4", "react-router-config": "^5.1.1", "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", "semver": "^7.5.4", - "serve-handler": "^6.1.5", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.9", + "serve-handler": "^6.1.6", + "tinypool": "^1.0.2", "tslib": "^2.6.0", "update-notifier": "^6.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.9.0", - "webpackbar": "^5.0.2" + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^6.0.1" }, "bin": { "docusaurus": "bin/docusaurus.mjs" @@ -2159,81 +3342,33 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@docusaurus/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@docusaurus/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@docusaurus/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@docusaurus/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, - "node_modules/@docusaurus/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@docusaurus/core/node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz", - "integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.8.1.tgz", + "integrity": "sha512-G7WyR2N6SpyUotqhGznERBK+x84uyhfMQM2MmDLs88bw4Flom6TY46HzkRkSEzaP9j80MbTN8naiL1fR17WQug==", + "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.4.38", + "postcss": "^8.5.4", "postcss-sort-media-queries": "^5.2.0", "tslib": "^2.6.0" }, @@ -2242,9 +3377,10 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz", - "integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.8.1.tgz", + "integrity": "sha512-2wjeGDhKcExEmjX8k1N/MRDiPKXGF2Pg+df/bDDPnnJWHXnVEZxXj80d6jcxp1Gpnksl0hF8t/ZQw9elqj2+ww==", + "license": "MIT", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -2253,85 +3389,22 @@ "node": ">=18.0" } }, - "node_modules/@docusaurus/logger/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@docusaurus/logger/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@docusaurus/logger/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@docusaurus/logger/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@docusaurus/logger/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@docusaurus/logger/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz", - "integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==", - "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.8.1.tgz", + "integrity": "sha512-DZRhagSFRcEq1cUtBMo4TKxSNo/W6/s44yhr8X+eoXqCLycFQUylebOMPseHi5tc4fkGJqwqpWJLz6JStU9L4w==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", "estree-util-value-to-estree": "^3.0.1", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", - "image-size": "^1.0.2", + "image-size": "^2.0.2", "mdast-util-mdx": "^3.0.0", "mdast-util-to-string": "^4.0.0", "rehype-raw": "^7.0.0", @@ -2351,21 +3424,22 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", - "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.8.1.tgz", + "integrity": "sha512-6xhvAJiXzsaq3JdosS7wbRt/PwEPWHr9eM4YNYqVlbgG1hSK3uQDXTVvQktasp3VO6BmfYWPozueLWuj4gB+vg==", + "license": "MIT", "dependencies": { - "@docusaurus/types": "3.4.0", + "@docusaurus/types": "3.8.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", "@types/react-router-dom": "*", - "react-helmet-async": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, "peerDependencies": { @@ -2374,22 +3448,24 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", - "integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "cheerio": "^1.0.0-rc.12", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.8.1.tgz", + "integrity": "sha512-vNTpMmlvNP9n3hGEcgPaXyvTljanAKIUkuG9URQ1DeuDup0OR7Ltvoc8yrmH+iMZJbcQGhUJF+WjHLwuk8HSdw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/theme-common": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", - "reading-time": "^1.5.0", + "schema-dts": "^1.1.2", "srcset": "^4.0.0", "tslib": "^2.6.0", "unist-util-visit": "^5.0.0", @@ -2400,28 +3476,32 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", - "integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.8.1.tgz", + "integrity": "sha512-oByRkSZzeGNQByCMaX+kif5Nl2vmtj2IHQI2fWjCfCootsdKZDPFLonhIp5s3IGJO7PLUfe0POyw0Xh/RrGXJA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/module-type-aliases": "3.8.1", + "@docusaurus/theme-common": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "schema-dts": "^1.1.2", "tslib": "^2.6.0", "utility-types": "^3.10.0", "webpack": "^5.88.1" @@ -2430,78 +3510,98 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", - "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.8.1.tgz", + "integrity": "sha512-a+V6MS2cIu37E/m7nDJn3dcxpvXb6TvgdNI22vJX8iUTp8eoMoPa0VArEbWvCxMY/xdC26WzNv4wZ6y0iIni/w==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.8.1.tgz", + "integrity": "sha512-VQ47xRxfNKjHS5ItzaVXpxeTm7/wJLFMOPo1BkmoMG4Cuz4nuI+Hs62+RMk1OqVog68Swz66xVPK8g9XTrBKRw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", - "integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.8.1.tgz", + "integrity": "sha512-nT3lN7TV5bi5hKMB7FK8gCffFTBSsBsAfV84/v293qAmnHOyg1nr9okEw8AiwcO3bl9vije5nsUvP0aRl2lpaw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", "fs-extra": "^11.1.1", - "react-json-view-lite": "^1.2.0", + "react-json-view-lite": "^2.3.0", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", - "integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.8.1.tgz", + "integrity": "sha512-Hrb/PurOJsmwHAsfMDH6oVpahkEGsx7F8CWMjyP/dw1qjqmdS9rcV1nYCGlM8nOtD3Wk/eaThzUB5TSZsGz+7Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", - "integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.8.1.tgz", + "integrity": "sha512-tKE8j1cEZCh8KZa4aa80zpSTxsC2/ZYqjx6AAfd8uA8VHZVw79+7OTEP2PoWi0uL5/1Is0LF5Vwxd+1fz5HlKg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2509,39 +3609,41 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", - "integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.8.1.tgz", + "integrity": "sha512-iqe3XKITBquZq+6UAXdb1vI0fPY5iIOitVjPQ581R1ZKpHr0qe+V6gVOrrcOHixPDD/BUKdYwkxFjpNiEN+vBw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", - "integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.8.1.tgz", + "integrity": "sha512-+9YV/7VLbGTq8qNkjiugIelmfUEVkTyLe6X8bWq7K5qPvGXAjno27QAfFq63mYfFFbJc7z+pudL63acprbqGzw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2550,61 +3652,89 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.8.1.tgz", + "integrity": "sha512-rW0LWMDsdlsgowVwqiMb/7tANDodpy1wWPwCcamvhY7OECReN3feoFwLjd/U4tKjNY3encj0AJSTxJA+Fpe+Gw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", - "integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/plugin-debug": "3.4.0", - "@docusaurus/plugin-google-analytics": "3.4.0", - "@docusaurus/plugin-google-gtag": "3.4.0", - "@docusaurus/plugin-google-tag-manager": "3.4.0", - "@docusaurus/plugin-sitemap": "3.4.0", - "@docusaurus/theme-classic": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-search-algolia": "3.4.0", - "@docusaurus/types": "3.4.0" + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.8.1.tgz", + "integrity": "sha512-yJSjYNHXD8POMGc2mKQuj3ApPrN+eG0rO1UPgSx7jySpYU+n4WjBikbrA2ue5ad9A7aouEtMWUoiSRXTH/g7KQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/plugin-content-blog": "3.8.1", + "@docusaurus/plugin-content-docs": "3.8.1", + "@docusaurus/plugin-content-pages": "3.8.1", + "@docusaurus/plugin-css-cascade-layers": "3.8.1", + "@docusaurus/plugin-debug": "3.8.1", + "@docusaurus/plugin-google-analytics": "3.8.1", + "@docusaurus/plugin-google-gtag": "3.8.1", + "@docusaurus/plugin-google-tag-manager": "3.8.1", + "@docusaurus/plugin-sitemap": "3.8.1", + "@docusaurus/plugin-svgr": "3.8.1", + "@docusaurus/theme-classic": "3.8.1", + "@docusaurus/theme-common": "3.8.1", + "@docusaurus/theme-search-algolia": "3.8.1", + "@docusaurus/types": "3.8.1" }, "engines": { "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", - "integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==", - "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.8.1.tgz", + "integrity": "sha512-bqDUCNqXeYypMCsE1VcTXSI1QuO4KXfx8Cvl6rYfY0bhhqN6d2WZlRkyLg/p6pm+DzvanqHOyYlqdPyP0iz+iw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/module-type-aliases": "3.8.1", + "@docusaurus/plugin-content-blog": "3.8.1", + "@docusaurus/plugin-content-docs": "3.8.1", + "@docusaurus/plugin-content-pages": "3.8.1", + "@docusaurus/theme-common": "3.8.1", + "@docusaurus/theme-translations": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", - "postcss": "^8.4.26", + "postcss": "^8.5.4", "prism-react-renderer": "^2.3.0", "prismjs": "^1.29.0", "react-router-dom": "^5.3.4", @@ -2616,22 +3746,20 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", - "integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==", - "dependencies": { - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/module-type-aliases": "3.4.0", - "@docusaurus/plugin-content-blog": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/plugin-content-pages": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.8.1.tgz", + "integrity": "sha512-UswMOyTnPEVRvN5Qzbo+l8k4xrd5fTFu2VPPfD6FcW/6qUtVLmJTQCktbAL3KJ0BVXGm5aJXz/ZrzqFuZERGPw==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.8.1", + "@docusaurus/module-type-aliases": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2645,25 +3773,27 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", - "integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==", - "dependencies": { - "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.4.0", - "@docusaurus/logger": "3.4.0", - "@docusaurus/plugin-content-docs": "3.4.0", - "@docusaurus/theme-common": "3.4.0", - "@docusaurus/theme-translations": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", - "algoliasearch": "^4.18.0", - "algoliasearch-helper": "^3.13.3", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.8.1.tgz", + "integrity": "sha512-NBFH5rZVQRAQM087aYSRKQ9yGEK9eHd+xOxQjqNpxMiV85OhJDD4ZGz6YJIod26Fbooy54UWVdzNU0TFeUUUzQ==", + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.9.0", + "@docusaurus/core": "3.8.1", + "@docusaurus/logger": "3.8.1", + "@docusaurus/plugin-content-docs": "3.8.1", + "@docusaurus/theme-common": "3.8.1", + "@docusaurus/theme-translations": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-validation": "3.8.1", + "algoliasearch": "^5.17.1", + "algoliasearch-helper": "^3.22.6", "clsx": "^2.0.0", "eta": "^2.2.0", "fs-extra": "^11.1.1", @@ -2675,14 +3805,15 @@ "node": ">=18.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz", - "integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.8.1.tgz", + "integrity": "sha512-OTp6eebuMcf2rJt4bqnvuwmm3NVXfzfYejL+u/Y1qwKhZPrjPoKWfk1CbOP5xH5ZOPkiAsx4dHdQBRJszK3z2g==", + "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2692,34 +3823,37 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", - "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.8.1.tgz", + "integrity": "sha512-ZPdW5AB+pBjiVrcLuw3dOS6BFlrG0XkS2lDGsj8TizcnREQg3J8cjsgfDviszOk4CweNfwo1AEELJkYaMUuOPg==", + "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", "@types/react": "*", "commander": "^5.1.0", "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "utility-types": "^3.10.0", - "webpack": "^5.88.1", + "webpack": "^5.95.0", "webpack-merge": "^5.9.0" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@docusaurus/utils": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz", - "integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==", - "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils-common": "3.4.0", - "@svgr/webpack": "^8.1.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.8.1.tgz", + "integrity": "sha512-P1ml0nvOmEFdmu0smSXOqTS1sxU5tqvnc0dA4MTKV39kye+bhQnjkIKEE18fNOvxjyB86k8esoCIFM3x4RykOQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.8.1", + "@docusaurus/types": "3.8.1", + "@docusaurus/utils-common": "3.8.1", "escape-string-regexp": "^4.0.0", + "execa": "5.1.1", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", "github-slugger": "^1.5.0", @@ -2729,9 +3863,9 @@ "js-yaml": "^4.1.0", "lodash": "^4.17.21", "micromatch": "^4.0.5", + "p-queue": "^6.6.2", "prompts": "^2.4.2", "resolve-pathname": "^3.0.0", - "shelljs": "^0.8.5", "tslib": "^2.6.0", "url-loader": "^4.1.1", "utility-types": "^3.10.0", @@ -2739,43 +3873,30 @@ }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-common": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz", - "integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.8.1.tgz", + "integrity": "sha512-zTZiDlvpvoJIrQEEd71c154DkcriBecm4z94OzEE9kz7ikS3J+iSlABhFXM45mZ0eN5pVqqr7cs60+ZlYLewtg==", + "license": "MIT", "dependencies": { + "@docusaurus/types": "3.8.1", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz", - "integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==", - "dependencies": { - "@docusaurus/logger": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-common": "3.4.0", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.8.1.tgz", + "integrity": "sha512-gs5bXIccxzEbyVecvxg6upTwaUbfa0KMmTj7HhHzc016AGyxH2o73k1/aOD0IFrdCsfJNt37MqNI47s2MgRZMA==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.8.1", + "@docusaurus/utils": "3.8.1", + "@docusaurus/utils-common": "3.8.1", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -2790,6 +3911,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", "dependencies": { "@emotion/memoize": "^0.8.1" } @@ -2797,17 +3919,20 @@ "node_modules/@emotion/memoize": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" }, "node_modules/@exodus/schemasafe": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", - "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", + "license": "MIT" }, "node_modules/@hapi/hoek": { "version": "9.3.0", @@ -2826,6 +3951,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -2837,6 +3963,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2849,81 +3976,24 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -2934,14 +4004,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -2952,14 +4014,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -3005,9 +4069,10 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", - "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", "dependencies": { "@types/mdx": "^2.0.0" }, @@ -3095,27 +4160,37 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" }, "node_modules/@redocly/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.3.tgz", + "integrity": "sha512-4P3iZse91TkBiY+Dx5DUgxQ9GXkVJf++cmI0MOyLDxV9b5MUBI4II6ES8zA5JCbO72nKAJxWrw4PUPW+YP3ZDQ==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js-replace": "^1.0.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@redocly/config": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.6.3.tgz", + "integrity": "sha512-hGWJgCsXRw0Ow4rplqRlUQifZvoSwZipkYnt11e3SeH1Eb23VUIDBcRuaQOUqy1wn0eevXkU2GzzQ8fbKdQ7Mg==", + "license": "MIT" + }, "node_modules/@redocly/openapi-core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.10.3.tgz", - "integrity": "sha512-4SnIWh8r3EM1ylcoHIJSnQnuvqRTpQMnf2RU3BfVdcCBa0A1uEyH6XSxgcO5ehxfQGuGGpUXJ+vPh32PUaQDkA==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.16.0.tgz", + "integrity": "sha512-z06h+svyqbUcdAaePq8LPSwTPlm6Ig7j2VlL8skPBYnJvyaQ2IN7x/JkOvRL4ta+wcOCBdAex5JWnZbKaNktJg==", + "license": "MIT", "dependencies": { "@redocly/ajv": "^8.11.0", + "@redocly/config": "^0.6.0", "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.4", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "lodash.isequal": "^4.5.0", @@ -3130,9 +4205,10 @@ } }, "node_modules/@redocly/openapi-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3141,6 +4217,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3169,12 +4246,14 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3186,6 +4265,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "license": "MIT", "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.1.0", @@ -3196,6 +4276,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3211,6 +4292,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3226,6 +4308,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3241,6 +4324,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3256,6 +4340,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3271,6 +4356,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3286,6 +4372,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -3301,6 +4388,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -3316,6 +4404,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", @@ -3341,6 +4430,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3360,6 +4450,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" @@ -3376,6 +4467,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3397,6 +4489,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", "dependencies": { "cosmiconfig": "^8.1.3", "deepmerge": "^4.3.1", @@ -3417,6 +4510,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@babel/plugin-transform-react-constant-elements": "^7.21.3", @@ -3450,6 +4544,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", "engines": { "node": ">=10.13.0" } @@ -3523,9 +4618,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3601,12 +4697,14 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -3615,6 +4713,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -3663,11 +4762,6 @@ "@types/node": "*" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" - }, "node_modules/@types/prismjs": { "version": "1.26.4", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", @@ -3735,6 +4829,7 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3777,7 +4872,15 @@ "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true }, "node_modules/@types/unist": { "version": "3.0.2", @@ -3793,9 +4896,10 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } @@ -3803,7 +4907,8 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", @@ -3811,145 +4916,162 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" }, "node_modules/accepts": { "version": "1.3.8", @@ -3983,9 +5105,10 @@ } }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3993,12 +5116,16 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/acorn-jsx": { @@ -4028,10 +5155,20 @@ "node": ">= 10.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4083,31 +5220,35 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", - "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.3", - "@algolia/cache-common": "4.23.3", - "@algolia/cache-in-memory": "4.23.3", - "@algolia/client-account": "4.23.3", - "@algolia/client-analytics": "4.23.3", - "@algolia/client-common": "4.23.3", - "@algolia/client-personalization": "4.23.3", - "@algolia/client-search": "4.23.3", - "@algolia/logger-common": "4.23.3", - "@algolia/logger-console": "4.23.3", - "@algolia/recommend": "4.23.3", - "@algolia/requester-browser-xhr": "4.23.3", - "@algolia/requester-common": "4.23.3", - "@algolia/requester-node-http": "4.23.3", - "@algolia/transporter": "4.23.3" + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.37.0.tgz", + "integrity": "sha512-y7gau/ZOQDqoInTQp0IwTOjkrHc4Aq4R8JgpmCleFwiLl+PbN2DMWoDUWZnrK8AhNJwT++dn28Bt4NZYNLAmuA==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.3.0", + "@algolia/client-abtesting": "5.37.0", + "@algolia/client-analytics": "5.37.0", + "@algolia/client-common": "5.37.0", + "@algolia/client-insights": "5.37.0", + "@algolia/client-personalization": "5.37.0", + "@algolia/client-query-suggestions": "5.37.0", + "@algolia/client-search": "5.37.0", + "@algolia/ingestion": "1.37.0", + "@algolia/monitoring": "1.37.0", + "@algolia/recommend": "5.37.0", + "@algolia/requester-browser-xhr": "5.37.0", + "@algolia/requester-fetch": "5.37.0", + "@algolia/requester-node-http": "5.37.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.1.tgz", - "integrity": "sha512-fSxJ4YreH4kOME9CnKazbAn2tK/rvBoV37ETd6nTt4j7QfkcnW+c+F22WfuE9Q/sRpvOMnUwU/BXAVEiwW7p/w==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz", + "integrity": "sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw==", + "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -4141,6 +5282,33 @@ "node": ">=8" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -4161,14 +5329,18 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -4186,7 +5358,8 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -4214,18 +5387,10 @@ "astring": "bin/astring" } }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "funding": [ { "type": "opencollective", @@ -4240,12 +5405,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4259,9 +5425,10 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -4278,17 +5445,19 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "license": "MIT", "dependencies": { "object.assign": "^4.1.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -4299,28 +5468,31 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4340,6 +5512,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.2.tgz", + "integrity": "sha512-NvcIedLxrs9llVpX7wI+Jz4Hn9vJQkCPKrTaHIE0sW/Rj1iq6Fzby4NbyTZjQJNoypBXNaG7tEHkTgONZpwgxQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -4365,9 +5546,10 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4377,7 +5559,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4391,6 +5573,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4399,6 +5582,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4406,111 +5590,49 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" } }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", "dependencies": { - "has-flag": "^4.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4528,9 +5650,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", + "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", "funding": [ { "type": "opencollective", @@ -4545,11 +5667,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "baseline-browser-mapping": "^2.8.2", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -4597,15 +5721,44 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -4617,12 +5770,14 @@ "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4651,6 +5806,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4659,6 +5815,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -4667,9 +5824,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001636", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", - "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "funding": [ { "type": "opencollective", @@ -4683,7 +5840,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/ccount": { "version": "2.0.1", @@ -4695,30 +5853,26 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", "engines": { "node": ">=10" } @@ -4763,6 +5917,7 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", @@ -4783,6 +5938,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", @@ -4843,7 +5999,8 @@ "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/clean-css": { "version": "5.3.3", @@ -4868,6 +6025,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4919,6 +6077,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -4928,45 +6087,17 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4980,6 +6111,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5023,27 +6155,34 @@ } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "license": "MIT" }, "node_modules/combine-promises": { "version": "1.2.0", @@ -5073,7 +6212,8 @@ "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "license": "ISC" }, "node_modules/compressible": { "version": "2.0.18", @@ -5095,22 +6235,32 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5124,10 +6274,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -5170,14 +6324,19 @@ } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5186,6 +6345,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5193,12 +6353,14 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5209,9 +6371,10 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/copy-text-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", - "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.1.tgz", + "integrity": "sha512-3am6cw+WOicd0+HyzhC4kYS02wHJUiVQXmAADxfUARKsHBkWl1Vl3QQEiILlSs8YcPS/C0+y/urCNEYQk+byWA==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -5223,6 +6386,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "license": "MIT", "dependencies": { "fast-glob": "^3.2.11", "glob-parent": "^6.0.1", @@ -5246,6 +6410,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -5257,6 +6422,7 @@ "version": "13.2.2", "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "license": "MIT", "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.3.0", @@ -5275,6 +6441,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -5293,11 +6460,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.25.3" }, "funding": { "type": "opencollective", @@ -5305,10 +6473,11 @@ } }, "node_modules/core-js-pure": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", - "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz", + "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", "hasInstallScript": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -5323,6 +6492,7 @@ "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -5345,9 +6515,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5382,10 +6553,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", "engines": { "node": ">=4" } @@ -5394,6 +6604,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -5401,10 +6612,73 @@ "postcss": "^8.0.9" } }, + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", @@ -5439,6 +6713,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "cssnano": "^6.0.1", @@ -5478,10 +6753,33 @@ } } }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -5497,6 +6795,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", @@ -5507,6 +6806,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" @@ -5526,10 +6826,27 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssdb": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.0.tgz", + "integrity": "sha512-lyATYGyvXwQ8h55WeQeEHXhI+47rl52pXSYkFK/ZrCbAJSgVIaPFjYc3RM8TpRHKk7W3wsAZImmLps+P5VyN9g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -5541,6 +6858,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "license": "MIT", "dependencies": { "cssnano-preset-default": "^6.1.2", "lilconfig": "^3.1.1" @@ -5560,6 +6878,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", "dependencies": { "autoprefixer": "^10.4.19", "browserslist": "^4.23.0", @@ -5580,6 +6899,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "css-declaration-sorter": "^7.2.0", @@ -5623,6 +6943,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -5634,6 +6955,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -5646,6 +6968,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -5658,7 +6981,8 @@ "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" }, "node_modules/csstype": { "version": "3.1.3", @@ -5671,11 +6995,12 @@ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5740,6 +7065,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5791,6 +7117,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -5803,31 +7130,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -5844,6 +7151,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -5857,48 +7165,19 @@ "node_modules/detect-port": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", "dependencies": { "address": "^1.0.1", - "debug": "^2.6.0" + "debug": "4" }, "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" }, "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node": ">= 4.0.0" } }, - "node_modules/detect-port-alt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -5934,37 +7213,41 @@ } }, "node_modules/docusaurus-plugin-redoc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-redoc/-/docusaurus-plugin-redoc-2.1.0.tgz", - "integrity": "sha512-uz1rjUTuUpTxg0Y3xX3GkBc0DYdTO6/Au6MMXxoTwzlJiHSq0+pAG/i729mDs1TOpEu/psSyJxhM0hv85Li75w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/docusaurus-plugin-redoc/-/docusaurus-plugin-redoc-2.5.0.tgz", + "integrity": "sha512-44sDhuXvItHnUuPdKswF3cRhiN5UW3YZxmMBsQLSfCYKcYr9tgWF2qvDfQoZO9i1DwpaYbIZ/RKMrSgny/iWYA==", + "license": "MIT", "dependencies": { - "@redocly/openapi-core": "1.10.3", - "redoc": "2.1.3" + "@redocly/openapi-core": "1.16.0", + "redoc": "2.4.0" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@docusaurus/utils": "^3.0.0" + "@docusaurus/utils": "^3.6.0" } }, "node_modules/docusaurus-theme-redoc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/docusaurus-theme-redoc/-/docusaurus-theme-redoc-2.0.2.tgz", - "integrity": "sha512-qa8svxKCipIvokNGctoQRP3Vz+HTF2Mnwg6xzY/W/LR5TYEIu3nuSvouLLR58uBrHKGzGpto5CEPDq0SvVvDlA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/docusaurus-theme-redoc/-/docusaurus-theme-redoc-2.5.0.tgz", + "integrity": "sha512-ykLmnnvE20Im3eABlIpUnXnT2gSHVAjgyy2fU2G8yecu7zqIE+G/SiBpBg/hrWMUycL31a8VSG7Ehkf3pg1u+A==", + "license": "MIT", "dependencies": { - "@redocly/openapi-core": "1.10.3", + "@redocly/openapi-core": "1.16.0", "clsx": "^1.2.1", "lodash": "^4.17.21", - "mobx": "^6.0.4", - "redoc": "2.1.3", - "styled-components": "^6.0.5" + "mobx": "^6.12.4", + "postcss": "^8.4.45", + "postcss-prefix-selector": "^1.16.1", + "redoc": "2.4.0", + "styled-components": "^6.1.11" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@docusaurus/theme-common": "^3.0.0", + "@docusaurus/theme-common": "^3.6.0", "webpack": "^5.0.0" } }, @@ -5972,6 +7255,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5988,6 +7272,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -6012,6 +7297,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -6023,14 +7309,19 @@ } }, "node_modules/dompurify": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.5.tgz", - "integrity": "sha512-FgbqnEPiv5Vdtwt6Mxl7XSylttCC03cqP5ldNT2z+Kj0nLxPHJH4+1Cyf5Jasxhw93Rl4Oo11qRoUV72fmya2Q==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -6071,6 +7362,20 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -6084,12 +7389,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.810", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.810.tgz", - "integrity": "sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==" + "version": "1.5.218", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", + "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -6099,7 +7406,8 @@ "node_modules/emojilib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==" + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "license": "MIT" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -6110,26 +7418,29 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6142,6 +7453,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -6153,17 +7465,16 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6181,15 +7492,29 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==" + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6323,9 +7648,10 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", - "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", + "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" }, @@ -6358,6 +7684,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -6377,6 +7704,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -6429,36 +7757,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6467,6 +7796,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/content-disposition": { @@ -6494,9 +7827,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6550,14 +7884,25 @@ "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "punycode": "^1.3.2" + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" } }, "node_modules/fastq": { @@ -6572,6 +7917,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", "dependencies": { "format": "^0.2.0" }, @@ -6595,6 +7941,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "license": "MIT", "dependencies": { "xml-js": "^1.6.11" }, @@ -6602,6 +7949,30 @@ "node": ">=0.4.0" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -6679,14 +8050,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6699,281 +8062,103 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreach": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", - "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", - "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=10", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" }, "engines": { - "node": ">= 8.9.0" + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { - "node": ">=6" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "license": "MIT" + }, "node_modules/form-data-encoder": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", @@ -7002,6 +8187,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", "engines": { "node": "*" }, @@ -7014,6 +8200,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7066,6 +8253,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -7074,20 +8262,27 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "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", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7099,7 +8294,21 @@ "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/get-stream": { "version": "6.0.1", @@ -7175,49 +8384,6 @@ "node": ">=10" } }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7238,11 +8404,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7342,11 +8509,12 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -7360,21 +8528,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7405,15 +8563,16 @@ } }, "node_modules/hast-util-from-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", - "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", - "hastscript": "^8.0.0", - "property-information": "^6.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" @@ -7423,10 +8582,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-from-parse5/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -7436,9 +8606,10 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", - "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -7529,6 +8700,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -7556,14 +8728,15 @@ } }, "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", + "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" }, "funding": { @@ -7571,6 +8744,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -7667,6 +8850,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "clean-css": "~5.3.2", @@ -7687,6 +8871,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", "engines": { "node": ">=14" } @@ -7706,6 +8891,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -7781,6 +8967,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -7802,6 +8989,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -7832,9 +9020,10 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -7868,7 +9057,8 @@ "node_modules/http2-client": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==" + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "license": "MIT" }, "node_modules/http2-wrapper": { "version": "2.2.1", @@ -7882,6 +9072,19 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7894,6 +9097,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7905,6 +9109,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -7921,12 +9126,10 @@ } }, "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", - "dependencies": { - "queue": "6.0.2" - }, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", "bin": { "image-size": "bin/image-size.js" }, @@ -7934,19 +9137,11 @@ "node": ">=16.x" } }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7978,14 +9173,16 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "license": "MIT", "engines": { "node": ">=12" } @@ -8015,14 +9212,6 @@ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -8064,7 +9253,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -8089,9 +9279,10 @@ } }, "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -8207,18 +9398,11 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -8261,18 +9445,11 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -8305,111 +9482,49 @@ "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", "engines": { - "node": ">=12" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -8420,18 +9535,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8466,6 +9574,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8487,14 +9596,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -8511,6 +9621,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "license": "MIT", "dependencies": { "foreach": "^2.0.4" } @@ -8598,9 +9709,10 @@ } }, "node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { "node": ">=14" }, @@ -8611,7 +9723,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/loader-runner": { "version": "4.3.0", @@ -8633,6 +9746,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "license": "MIT", "dependencies": { "p-locate": "^6.0.0" }, @@ -8651,22 +9765,27 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" }, "node_modules/longest-streak": { "version": "3.1.0", @@ -8711,6 +9830,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } @@ -8718,12 +9838,14 @@ "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "license": "MIT" }, "node_modules/mark.js": { "version": "8.11.1", "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "license": "MIT" }, "node_modules/markdown-extensions": { "version": "2.0.0", @@ -8737,9 +9859,10 @@ } }, "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8749,6 +9872,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -8756,13 +9880,24 @@ "node": ">= 12" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", - "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", + "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", @@ -8776,9 +9911,10 @@ } }, "node_modules/mdast-util-find-and-replace": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", - "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", @@ -8794,6 +9930,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8843,6 +9980,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -8860,6 +9998,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8868,9 +10007,10 @@ } }, "node_modules/mdast-util-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", - "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", @@ -8886,9 +10026,10 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -8902,9 +10043,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8915,15 +10056,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8933,12 +10075,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/mdast-util-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", @@ -8955,6 +10099,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", @@ -8969,6 +10114,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -8985,6 +10131,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", @@ -9137,12 +10284,14 @@ "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9159,9 +10308,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -9305,9 +10458,10 @@ ] }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9323,9 +10477,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9336,15 +10490,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9355,15 +10510,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9373,12 +10529,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-frontmatter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", @@ -9391,9 +10549,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9404,15 +10562,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9422,12 +10581,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", @@ -9444,9 +10605,10 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", @@ -9459,9 +10621,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9472,15 +10634,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9490,12 +10653,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", @@ -9512,9 +10677,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9525,15 +10690,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9544,15 +10710,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9562,12 +10729,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -9582,9 +10751,9 @@ } }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9594,12 +10763,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", - "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9613,9 +10784,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9626,15 +10797,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9645,15 +10817,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9663,12 +10836,14 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, @@ -9678,9 +10853,10 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", - "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9694,9 +10870,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9707,15 +10883,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9726,15 +10903,16 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9744,7 +10922,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.0", @@ -10165,6 +11344,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" @@ -10183,7 +11363,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-factory-title": { "version": "2.0.0", @@ -10347,6 +11528,7 @@ "url": "https://opencollective.com/unified" } ], + "license": "MIT", "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" @@ -10365,7 +11547,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-chunked": { "version": "2.0.0", @@ -10785,7 +11968,8 @@ "type": "OpenCollective", "url": "https://opencollective.com/unified" } - ] + ], + "license": "MIT" }, "node_modules/micromark-util-types": { "version": "2.0.0", @@ -10856,9 +12040,10 @@ ] }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -10871,6 +12056,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -10917,9 +12103,10 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -10960,28 +12147,30 @@ } }, "node_modules/mobx": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.12.4.tgz", - "integrity": "sha512-uIymg89x+HmItX1p3MG+d09irn2k63J6biftZ5Ok+UpNojS1I3NJPLfcmJT9ANnUltNlHi+HQqrVyxiAN8ISYg==", + "version": "6.13.7", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", + "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" } }, "node_modules/mobx-react": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.6.0.tgz", - "integrity": "sha512-+HQUNuh7AoQ9ZnU6c4rvbiVVl+wEkb9WqYsVDzGLng+Dqj1XntHu79PvEWKtSMoMj67vFp/ZPXcElosuJO8ckA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.2.0.tgz", + "integrity": "sha512-dkGWCx+S0/1mfiuFfHRH8D9cplmwhxOV5CkXMp38u6rQGG2Pv3FWYztS0M7ncR6TyPRQKaTG/pnitInoYE9Vrw==", + "license": "MIT", "dependencies": { - "mobx-react-lite": "^3.4.0" + "mobx-react-lite": "^4.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" }, "peerDependencies": { - "mobx": "^6.1.0", - "react": "^16.8.0 || ^17 || ^18" + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" }, "peerDependenciesMeta": { "react-dom": { @@ -10993,16 +12182,20 @@ } }, "node_modules/mobx-react-lite": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.4.3.tgz", - "integrity": "sha512-NkJREyFTSUXR772Qaai51BnE1voWx56LOL80xG7qkZr6vo8vEaLF3sz1JNUVh+rxmUzxYaqOhfuxTfqUh0FXUg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", + "integrity": "sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.4.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" }, "peerDependencies": { - "mobx": "^6.1.0", - "react": "^16.8.0 || ^17 || ^18" + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" }, "peerDependenciesMeta": { "react-dom": { @@ -11022,9 +12215,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -11039,15 +12233,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -11078,9 +12273,10 @@ } }, "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "license": "MIT", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -11095,6 +12291,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -11114,6 +12311,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "license": "MIT", "dependencies": { "http2-client": "^1.2.5" }, @@ -11133,14 +12331,16 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", + "license": "MIT", "dependencies": { "es6-promise": "^3.2.1" } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -11154,6 +12354,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -11169,37 +12370,122 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/null-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" }, "engines": { - "node": ">=8" + "node": ">=8.9.0" } }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { - "boolbase": "^1.0.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "license": "BSD-3-Clause", "dependencies": { "fast-safe-stringify": "^2.0.7" } @@ -11208,6 +12494,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "license": "BSD-3-Clause", "dependencies": { "@exodus/schemasafe": "^1.0.0-rc.2", "should": "^13.2.1", @@ -11221,6 +12508,7 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "license": "BSD-3-Clause", "dependencies": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", @@ -11239,6 +12527,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "license": "BSD-3-Clause", "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -11247,6 +12536,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "license": "BSD-3-Clause", "dependencies": { "call-me-maybe": "^1.0.1", "oas-kit-common": "^1.0.8", @@ -11270,9 +12560,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -11284,18 +12575,22 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -11314,6 +12609,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -11322,9 +12618,10 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11368,11 +12665,13 @@ } }, "node_modules/openapi-sampler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.5.1.tgz", - "integrity": "sha512-tIWIrZUKNAsbqf3bd9U1oH6JEXo8LNYuDlXw26By67EygpjT+ArFnsxxyTMjFWRfbqo5ozkvgSQDK69Gd8CddA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.6.1.tgz", + "integrity": "sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.7", + "fast-xml-parser": "^4.5.0", "json-pointer": "0.6.2" } }, @@ -11392,10 +12691,20 @@ "node": ">=12.20" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" }, @@ -11410,6 +12719,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "license": "MIT", "dependencies": { "p-limit": "^4.0.0" }, @@ -11424,6 +12734,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" }, @@ -11434,6 +12745,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -11446,12 +12773,16 @@ "node": ">=8" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/package-json": { @@ -11484,6 +12815,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -11519,6 +12851,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -11535,31 +12868,46 @@ "node_modules/parse-numeric-range": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", "dependencies": { - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "parse5": "^7.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -11580,12 +12928,14 @@ "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -11601,7 +12951,8 @@ "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { "version": "3.1.1", @@ -11614,12 +12965,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", "dependencies": { "isarray": "0.0.1" } @@ -11632,273 +12985,680 @@ "node": ">=8" } }, - "node_modules/perfect-scrollbar": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", - "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" - }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "node_modules/perfect-scrollbar": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", + "integrity": "sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==", + "license": "MIT" + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.11.tgz", + "integrity": "sha512-zfqoUSaHMko/k2PA9xnaydVTHqYv5vphq5Q2AHcG/dCdv/OkHYWcVWfVTBKZ526uzT8L7NghuvSw3C9PxlKnLg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=8.6" + "node": "^14 || ^16 || >=18.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "find-up": "^6.3.0" + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "node_modules/postcss-custom-properties": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "find-up": "^3.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/postcss-custom-selectors": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "locate-path": "^3.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "p-try": "^2.0.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { - "p-limit": "^2.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "license": "MIT", "engines": { - "node": ">=4" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "license": "MIT", "engines": { - "node": ">=4" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/polished": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", - "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.17.8" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": ">=10" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/postcss-double-position-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.3.tgz", + "integrity": "sha512-Dl0Z9sdbMwrPslgOaGBZRGo3TASmmgTcqcUODr82MTYyJk6devXZM6MlQjpQKMJqlLJ6oL1w78U7IXFdPA5+ug==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "github", + "url": "https://github.com/sponsors/csstools" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], + "license": "MIT-0", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4" } }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=4" } }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "engines": { - "node": "^14 || ^16 || >=18.0" + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "peerDependencies": { - "postcss": "^8.4.31" + "engines": { + "node": ">=4" } }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.1.0" } }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "node_modules/postcss-lab-function": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.11.tgz", + "integrity": "sha512-BEA4jId8uQe1gyjZZ6Bunb6ZsH2izks+v25AxQJDBtigXCjTLmCPWECwQpLTtcxH589MVxhs/9TAmRC6lUEmXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, "node_modules/postcss-loader": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "license": "MIT", "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", @@ -11916,10 +13676,36 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-merge-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -11935,6 +13721,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^6.1.1" @@ -11950,6 +13737,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -11967,6 +13755,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -11981,6 +13770,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "license": "MIT", "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^4.0.2", @@ -11997,6 +13787,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "cssnano-utils": "^4.0.2", @@ -12013,6 +13804,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -12027,6 +13819,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -12035,12 +13828,13 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -12050,12 +13844,26 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -12064,10 +13872,24 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, @@ -12078,10 +13900,95 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-normalize-charset": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -12093,6 +14000,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12107,6 +14015,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12121,6 +14030,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12135,6 +14045,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12149,6 +14060,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12163,6 +14075,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -12178,6 +14091,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12192,6 +14106,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12202,10 +14117,33 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/postcss-ordered-values": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "license": "MIT", "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -12217,10 +14155,207 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-prefix-selector": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz", + "integrity": "sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==", + "license": "MIT", + "peerDependencies": { + "postcss": ">4 <9" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.3.1.tgz", + "integrity": "sha512-8ZOOWVwQ0iMpfEYkYo+U6W7fE2dJ/tP6dtEFwPJ66eB5JjnFupfYh+y6zo+vWDO72nGhKOVdxwhTjfzcSNRg4Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-alpha-function": "^1.0.0", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.11", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.0", + "@csstools/postcss-color-mix-function": "^3.0.11", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.1", + "@csstools/postcss-content-alt-text": "^2.0.7", + "@csstools/postcss-exponential-functions": "^2.0.9", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.11", + "@csstools/postcss-hwb-function": "^4.0.11", + "@csstools/postcss-ic-unit": "^4.0.3", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.10", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.4", + "@csstools/postcss-media-minmax": "^2.0.9", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.11", + "@csstools/postcss-progressive-custom-properties": "^4.2.0", + "@csstools/postcss-random-function": "^2.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.11", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.4", + "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", + "@csstools/postcss-trigonometric-functions": "^4.0.9", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.21", + "browserslist": "^4.25.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.3", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.4.0", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.11", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.6", + "postcss-custom-properties": "^14.0.6", + "postcss-custom-selectors": "^8.0.5", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.3", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.11", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.2", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-reduce-idents": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12235,6 +14370,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" @@ -12250,6 +14386,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -12260,10 +14397,58 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -12276,6 +14461,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", "dependencies": { "sort-css-media-queries": "2.2.0" }, @@ -12290,6 +14476,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^3.2.0" @@ -12305,6 +14492,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -12318,12 +14506,14 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/postcss-zindex": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -12344,14 +14534,16 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -12361,9 +14553,10 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -12429,11 +14622,6 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -12449,11 +14637,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -12462,14 +14651,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dependencies": { - "inherits": "~2.0.3" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12512,6 +14693,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -12520,6 +14702,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12534,6 +14717,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12571,180 +14755,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-dev-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-dev-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/react-dev-utils/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -12757,11 +14767,6 @@ "react": "^18.3.1" } }, - "node_modules/react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -12789,14 +14794,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", - "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + "react": "^18.0.0 || ^19.0.0" } }, "node_modules/react-loadable": { @@ -12875,23 +14881,16 @@ } }, "node_modules/react-tabs": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz", - "integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.0.tgz", + "integrity": "sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==", + "license": "MIT", "dependencies": { - "clsx": "^1.1.0", + "clsx": "^2.0.0", "prop-types": "^15.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-0 || ^18.0.0" - } - }, - "node_modules/react-tabs/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" + "react": "^18.0.0 || ^19.0.0" } }, "node_modules/readable-stream": { @@ -12918,58 +14917,32 @@ "node": ">=8.10.0" } }, - "node_modules/reading-time": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", - "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/redoc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.1.3.tgz", - "integrity": "sha512-d7F9qLLxaiFW4GC03VkwlX9wuRIpx9aiIIf3o6mzMnqPfhxrn2IRKGndrkJeVdItgCfmg9jXZiFEowm60f1meQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.4.0.tgz", + "integrity": "sha512-rFlfzFVWS9XJ6aYAs/bHnLhHP5FQEhwAHDBVgwb9L2FqDQ8Hu8rQ1G84iwaWXxZfPP9UWn7JdWkxI6MXr2ZDjw==", + "license": "MIT", "dependencies": { - "@redocly/openapi-core": "^1.0.0-rc.2", - "classnames": "^2.3.1", + "@redocly/openapi-core": "^1.4.0", + "classnames": "^2.3.2", "decko": "^1.2.0", - "dompurify": "^2.2.8", - "eventemitter3": "^4.0.7", + "dompurify": "^3.0.6", + "eventemitter3": "^5.0.1", "json-pointer": "^0.6.2", "lunr": "^2.3.9", "mark.js": "^8.11.1", - "marked": "^4.0.15", - "mobx-react": "^7.2.0", - "openapi-sampler": "^1.3.1", + "marked": "^4.3.0", + "mobx-react": "^9.1.1", + "openapi-sampler": "^1.5.0", "path-browserify": "^1.0.1", "perfect-scrollbar": "^1.5.5", - "polished": "^4.1.3", - "prismjs": "^1.27.0", - "prop-types": "^15.7.2", - "react-tabs": "^4.3.0", + "polished": "^4.2.2", + "prismjs": "^1.29.0", + "prop-types": "^15.8.1", + "react-tabs": "^6.0.2", "slugify": "~1.4.7", "stickyfill": "^1.1.1", - "swagger2openapi": "^7.0.6", + "swagger2openapi": "^7.0.8", "url-template": "^2.0.8" }, "engines": { @@ -12979,31 +14952,39 @@ "peerDependencies": { "core-js": "^3.1.4", "mobx": "^6.0.4", - "react": "^16.8.4 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", + "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" } }, + "node_modules/redoc/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/redocusaurus": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/redocusaurus/-/redocusaurus-2.1.0.tgz", - "integrity": "sha512-PJ7Tlx9CVHjXdWDvUZzyZ2ymwF4uais5MTXZI0iTI13T7ADa6Zdcd5MhcnhBdDBWL8/jrSHl9+X1af1BsnP9Qg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/redocusaurus/-/redocusaurus-2.5.0.tgz", + "integrity": "sha512-QWJX2hgnEfSDb7fZzS4iZe6aqdAvm/XLCsNv6RkgDw6Pl/lsTZKipP2n1r5QS1CC5hY8eAwsjVXeF7B03vkz2g==", + "license": "MIT", "dependencies": { - "docusaurus-plugin-redoc": "2.1.0", - "docusaurus-theme-redoc": "2.0.2" + "docusaurus-plugin-redoc": "2.5.0", + "docusaurus-theme-redoc": "2.5.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@docusaurus/theme-common": "^3.0.0", - "@docusaurus/utils": "^3.0.0" + "@docusaurus/theme-common": "^3.6.0", + "@docusaurus/utils": "^3.6.0" } }, "node_modules/reftools": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "license": "BSD-3-Clause", "funding": { "url": "https://github.com/Mermade/oas-kit?sponsor=1" } @@ -13011,12 +14992,14 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -13024,30 +15007,18 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.3.1.tgz", + "integrity": "sha512-DzcswPr252wEr7Qz8AyAVbfyBDKLoYp6eRA1We2Fa9qirRFSdtkP5sHr3yglDKy2BbA0fd2T+j/CUSKes3FeVQ==", + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -13078,29 +15049,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", @@ -13120,9 +15103,10 @@ } }, "node_modules/remark-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", - "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", @@ -13138,6 +15122,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.2", "emoticon": "^4.0.1", @@ -13153,6 +15138,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-frontmatter": "^2.0.0", @@ -13165,9 +15151,10 @@ } }, "node_modules/remark-gfm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", - "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", @@ -13229,6 +15216,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", @@ -13332,10 +15320,20 @@ "entities": "^2.0.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -13362,17 +15360,21 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13386,6 +15388,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -13441,15 +15444,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rtl-detect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" - }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -13507,12 +15506,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" }, "node_modules/scheduler": { "version": "0.23.2", @@ -13522,10 +15523,17 @@ "loose-envify": "^1.1.0" } }, + "node_modules/schema-dts": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "license": "Apache-2.0" + }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -13533,7 +15541,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -13541,9 +15549,10 @@ } }, "node_modules/search-insights": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", - "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", "peer": true }, "node_modules/section-matter": { @@ -13576,9 +15585,10 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -13601,9 +15611,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13627,6 +15638,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -13634,17 +15646,23 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -13658,24 +15676,25 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/serve-index": { "version": "1.9.1", @@ -13748,14 +15767,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -13780,7 +15800,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -13822,29 +15843,14 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "license": "MIT", "dependencies": { "should-equal": "^2.0.0", "should-format": "^3.0.3", @@ -13857,6 +15863,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "license": "MIT", "dependencies": { "should-type": "^1.4.0" } @@ -13865,6 +15872,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "license": "MIT", "dependencies": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" @@ -13873,12 +15881,14 @@ "node_modules/should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==" + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "license": "MIT" }, "node_modules/should-type-adaptors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "license": "MIT", "dependencies": { "should-type": "^1.3.0", "should-util": "^1.0.0" @@ -13887,17 +15897,73 @@ "node_modules/should-util": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "license": "MIT" }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "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" }, "engines": { "node": ">= 0.4" @@ -13933,6 +15999,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "license": "MIT", "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", @@ -13950,12 +16017,14 @@ "node_modules/sitemap/node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" }, "node_modules/skin-tone": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "license": "MIT", "dependencies": { "unicode-emoji-modifier-base": "^1.0.0" }, @@ -13975,6 +16044,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", + "license": "MIT", "engines": { "node": ">=8.0.0" } @@ -13983,6 +16053,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -14002,6 +16073,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", "engines": { "node": ">= 6.3.0" } @@ -14015,9 +16087,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -14085,6 +16158,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -14096,14 +16170,16 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "license": "MIT" }, "node_modules/stickyfill": { "version": "1.1.1", @@ -14176,6 +16252,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", @@ -14216,6 +16293,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -14223,6 +16301,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/style-to-object": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", @@ -14232,16 +16322,17 @@ } }, "node_modules/styled-components": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", - "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "license": "MIT", "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", "@types/stylis": "4.2.5", "css-to-react-native": "3.2.0", "csstype": "3.1.3", - "postcss": "8.4.38", + "postcss": "8.4.49", "shallowequal": "1.1.0", "stylis": "4.3.2", "tslib": "2.6.2" @@ -14258,15 +16349,45 @@ "react-dom": ">= 16.8.0" } }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/styled-components/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" }, "node_modules/stylehacks": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "license": "MIT", "dependencies": { "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.16" @@ -14281,23 +16402,26 @@ "node_modules/stylis": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -14308,12 +16432,14 @@ "node_modules/svg-parser": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" }, "node_modules/svgo": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "license": "MIT", "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -14338,6 +16464,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", "engines": { "node": ">= 10" } @@ -14346,6 +16473,7 @@ "version": "7.0.8", "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "license": "BSD-3-Clause", "dependencies": { "call-me-maybe": "^1.0.1", "node-fetch": "^2.6.1", @@ -14394,15 +16522,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -14426,37 +16555,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -14470,28 +16568,6 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14511,11 +16587,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -14531,12 +16602,13 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": "^18.0.0 || >=20.0.0" } }, "node_modules/to-regex-range": { @@ -14554,6 +16626,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -14569,7 +16642,8 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/trim-lines": { "version": "3.0.1", @@ -14609,6 +16683,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -14621,6 +16696,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14629,6 +16705,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -14644,28 +16721,16 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14674,6 +16739,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14682,6 +16748,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -14691,9 +16758,10 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14702,6 +16770,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", "engines": { "node": ">=4" } @@ -14838,14 +16907,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -14860,9 +16930,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -14949,6 +17020,12 @@ "punycode": "^2.1.0" } }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "license": "MIT" + }, "node_modules/uri-js/node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -15063,7 +17140,17 @@ "node_modules/url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -15127,9 +17214,10 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" @@ -15176,6 +17264,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -15184,23 +17273,26 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.92.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz", - "integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -15210,11 +17302,11 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -15416,41 +17508,14 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", "engines": { "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -15470,102 +17535,76 @@ "node": ">= 0.6" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpackbar": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", - "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.3", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.0.1" + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.21.3" }, "peerDependencies": { "webpack": "3 || 4 || 5" } }, - "node_modules/webpackbar/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, - "node_modules/webpackbar/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "repeat-string": "^1.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/webpackbar/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpackbar/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/webpackbar/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { "node": ">=8" } }, - "node_modules/webpackbar/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/websocket-driver": { @@ -15593,6 +17632,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -15734,6 +17774,7 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", "dependencies": { "sax": "^1.2.4" }, @@ -15745,6 +17786,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } @@ -15752,12 +17794,14 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -15765,12 +17809,14 @@ "node_modules/yaml-ast-parser": { "version": "0.0.43", "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==" + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "license": "Apache-2.0" }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -15788,6 +17834,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } @@ -15795,12 +17842,14 @@ "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -15811,9 +17860,10 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "license": "MIT", "engines": { "node": ">=12.20" }, diff --git a/docs/package.json b/docs/package.json index c542bfb1..1596d414 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "smriti", - "version": "24.06.24", + "version": "25.09.30", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -14,20 +14,21 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@babel/traverse": "^7.24.7", - "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-google-gtag": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@mdx-js/react": "^3.0.1", + "@babel/traverse": "^7.28.4", + "@docusaurus/core": "^3.6.0", + "@docusaurus/plugin-google-gtag": "^3.6.0", + "@docusaurus/preset-classic": "^3.6.0", + "@docusaurus/utils": "^3.6.0", + "@mdx-js/react": "^3.1.1", "loader-utils": "^3.3.1", - "prism-react-renderer": "^2.3.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "redocusaurus": "^2.0.2", - "semver": "^7.6.0" + "prism-react-renderer": "^2.4.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "redocusaurus": "^2.5.0", + "semver": "^7.7.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.0.1" + "@docusaurus/module-type-aliases": "^3.6.0" }, "browserslist": { "production": [ @@ -42,9 +43,9 @@ ] }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "overrides": { "got": "^12.1.0" } -} +} \ No newline at end of file diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 576c3209..eabf5aa2 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -9,32 +9,31 @@ font-family: "DM Sans", sans-serif; --ifm-font-family-base: "DM Sans", sans-serif; --ifm-font-family-monospace: "Fira Code", monospace; - --ifm-color-primary: #3d9ae8; - --ifm-color-primary-dark: #238ce5; - --ifm-color-primary-darker: #1a85df; - --ifm-color-primary-darkest: #166eb7; - --ifm-color-primary-light: #57a8eb; - --ifm-color-primary-lighter: #64aeed; - --ifm-color-primary-lightest: #8cc3f1; + --ifm-color-primary: #0b81e0; + --ifm-color-primary-dark: #0a74ca; + --ifm-color-primary-darker: #0967b3; + --ifm-color-primary-darkest: #085a9d; + --ifm-color-primary-light: #0f8ef3; + --ifm-color-primary-lighter: #2698f4; + --ifm-color-primary-lightest: #3ca3f5; --ifm-code-font-size: 90%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { - --ifm-color-primary: #3d9ae8; - --ifm-color-primary-dark: #238ce5; - --ifm-color-primary-darker: #1a85df; - --ifm-color-primary-darkest: #166eb7; - --ifm-color-primary-light: #57a8eb; - --ifm-color-primary-lighter: #64aeed; - --ifm-color-primary-lightest: #8cc3f1; - --ifm-background-color: #000000; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-color-primary: #0b81e0; + --ifm-color-primary-dark: #0a74ca; + --ifm-color-primary-darker: #096ebe; + --ifm-color-primary-darkest: #085a9d; + --ifm-color-primary-light: #0f8ef3; + --ifm-color-primary-lighter: #1a93f4; + --ifm-color-primary-lightest: #3ca3f5; + --ifm-navbar-background-color: #0e1f31 !important; + --ifm-background-color: #0e1f31 !important; } .footer--dark { - --ifm-footer-background-color: #071320; + --ifm-footer-background-color: #0e1f31; } .header-github-link:hover { diff --git a/docs/static/img/architecture.jpeg b/docs/static/img/architecture.jpeg index 82696071..741f58c7 100644 Binary files a/docs/static/img/architecture.jpeg and b/docs/static/img/architecture.jpeg differ diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico index b90ee5ea..e931603f 100644 Binary files a/docs/static/img/favicon.ico and b/docs/static/img/favicon.ico differ diff --git a/docs/static/img/logo-white.png b/docs/static/img/logo-white.png index f73afec7..a04f0e4f 100644 Binary files a/docs/static/img/logo-white.png and b/docs/static/img/logo-white.png differ diff --git a/docs/static/img/logo.png b/docs/static/img/logo.png index a7d3027f..deea73a4 100644 Binary files a/docs/static/img/logo.png and b/docs/static/img/logo.png differ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 4b1b6b4b..d5c39d28 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Smriti API description: Smarter Home for all your Photos and Videos - version: 24.06.24 + version: 25.09.30 servers: - url: https://localhost:5001 tags: @@ -29,24 +29,24 @@ paths: description: Get version of Smriti operationId: getVersion responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Version' + $ref: "#/components/schemas/Version" /disk: get: summary: Get disk usage of Smriti description: Get disk usage of Smriti operationId: getDisk responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Disk' + $ref: "#/components/schemas/Disk" /v1/features: get: summary: Get list of enabled features @@ -55,12 +55,12 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Features' + $ref: "#/components/schemas/Features" /v1/yearsAgo/{monthDate}/mediaItems: get: tags: @@ -78,19 +78,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/search: get: @@ -107,17 +107,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/mediaItems: get: @@ -141,36 +141,48 @@ paths: description: MediaItem Type schema: type: string - example: video + enum: ["UNKNOWN", "PHOTO", "VIDEO"] + example: PHOTO - name: category in: query description: MediaItem Category schema: type: string - example: live + enum: + [ + "DEFAULT", + "SCREENSHOT", + "PANORAMA", + "SLOW", + "MOTION", + "LIVE", + "TIMELAPSE", + ] + example: SCREENSHOT - name: status in: query description: MediaItem Status schema: type: string - example: FAILED + enum: ["UNSPECIFIED", "PROCESSING", "READY", "FAILED"] + example: READY summary: Get all existing mediaItems description: Get all existing mediaItems operationId: getMediaItems security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -181,13 +193,13 @@ paths: security: - bearerAuth: [] responses: - '201': + "201": description: Created - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/mediaItems/{id}: get: @@ -207,19 +219,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItem' - '400': + $ref: "#/components/schemas/MediaItem" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error put: tags: @@ -243,17 +255,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MediaItemRequest' + $ref: "#/components/schemas/MediaItemRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error delete: tags: @@ -272,15 +284,15 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/mediaItems/{id}/places: get: @@ -300,51 +312,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Places' - '400': + $ref: "#/components/schemas/Places" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': - description: Internal Server Error - /v1/mediaItems/{id}/things: - get: - tags: - - MediaItems - parameters: - - name: id - in: path - description: ID of mediaItem - required: true - schema: - type: string - format: uuid - summary: Get things from an existing mediaItem - description: Get things from an existing mediaItem - operationId: getMediaItemThings - security: - - bearerAuth: [] - responses: - '200': - description: Success - content: - application/json: - schema: - $ref: '#/components/schemas/Things' - '400': - description: Bad Request - '401': - description: Unauthenticated - '404': - description: Not Found - '500': + "500": description: Internal Server Error /v1/mediaItems/{id}/people: get: @@ -364,19 +344,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/People' - '400': + $ref: "#/components/schemas/People" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/mediaItems/{id}/albums: get: @@ -396,19 +376,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Albums' - '400': + $ref: "#/components/schemas/Albums" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/favourites: get: @@ -420,17 +400,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -446,15 +426,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MediaItemsRequest' + $ref: "#/components/schemas/MediaItemsRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error delete: tags: @@ -465,13 +445,13 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/hidden: get: @@ -483,17 +463,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -509,15 +489,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MediaItemsRequest' + $ref: "#/components/schemas/MediaItemsRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error delete: tags: @@ -528,13 +508,13 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/trash: get: @@ -546,17 +526,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -572,15 +552,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MediaItemsRequest' + $ref: "#/components/schemas/MediaItemsRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error delete: tags: @@ -591,13 +571,13 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/explore/places: get: @@ -609,17 +589,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Places' - '400': + $ref: "#/components/schemas/Places" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/explore/places/{id}/mediaItems: get: @@ -639,69 +619,17 @@ paths: security: - bearerAuth: [] responses: - '200': - description: Success - content: - application/json: - schema: - $ref: '#/components/schemas/MediaItems' - '400': - description: Bad Request - '401': - description: Unauthenticated - '500': - description: Internal Server Error - /v1/explore/things: - get: - tags: - - Explore - summary: Get all thing entities - description: Get all thing entities - operationId: getThings - security: - - bearerAuth: [] - responses: - '200': - description: Success - content: - application/json: - schema: - $ref: '#/components/schemas/Things' - '400': - description: Bad Request - '401': - description: Unauthenticated - '500': - description: Internal Server Error - /v1/explore/things/{id}/mediaItems: - get: - tags: - - Explore - parameters: - - name: id - in: path - description: ID of thing - required: true - schema: - type: string - format: uuid - summary: Get mediaItems of a thing entity - description: Get mediaItems of a thing entity - operationId: getThingMediaItems - security: - - bearerAuth: [] - responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/explore/people: get: @@ -713,17 +641,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/People' - '400': + $ref: "#/components/schemas/People" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/explore/people/{id}: put: @@ -739,7 +667,7 @@ paths: format: uuid summary: Update a people entity description: Update a people entity - operationId: updatePeople + operationId: updatePerson security: - bearerAuth: [] requestBody: @@ -748,17 +676,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PeopleRequest' + $ref: "#/components/schemas/PeopleRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/explore/people/{id}/mediaItems: get: @@ -774,21 +702,21 @@ paths: format: uuid summary: Get mediaItems of a people entity description: Get mediaItems of a people entity - operationId: getPeopleMediaItems + operationId: getPersonMediaItems security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/albums: get: @@ -826,17 +754,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Albums' - '400': + $ref: "#/components/schemas/Albums" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -852,15 +780,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AlbumRequest' + $ref: "#/components/schemas/AlbumRequest" responses: - '201': + "201": description: Created - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/albums/{id}: get: @@ -880,19 +808,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Album' - '400': + $ref: "#/components/schemas/Album" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error put: tags: @@ -916,17 +844,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AlbumRequest' + $ref: "#/components/schemas/AlbumRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error delete: tags: @@ -945,15 +873,15 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/albums/{id}/mediaItems: get: @@ -973,19 +901,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error post: tags: @@ -1009,17 +937,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/MediaItemsRequest' + $ref: "#/components/schemas/MediaItemsRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error delete: tags: @@ -1038,15 +966,15 @@ paths: security: - bearerAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/sharing/{id}: get: @@ -1064,19 +992,19 @@ paths: description: Get an existing shared album operationId: getSharedAlbum responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Album' - '400': + $ref: "#/components/schemas/Album" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/sharing/{id}/mediaItems: get: @@ -1094,19 +1022,19 @@ paths: description: Get mediaItems of an existing shared album operationId: getSharedAlbumMediaItems responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/MediaItems' - '400': + $ref: "#/components/schemas/MediaItems" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/auth/login: post: @@ -1121,21 +1049,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AuthRequest' + $ref: "#/components/schemas/AuthRequest" responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/AuthResponse' - '400': + $ref: "#/components/schemas/AuthResponse" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/auth/refresh: post: @@ -1145,19 +1073,19 @@ paths: description: User Refresh Token operationId: authRefresh responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/AuthResponse' - '400': + $ref: "#/components/schemas/AuthResponse" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/auth/logout: post: @@ -1167,15 +1095,15 @@ paths: description: User Logout operationId: authLogout responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/users: get: @@ -1187,17 +1115,17 @@ paths: security: - basicAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Users' - '400': + $ref: "#/components/schemas/Users" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -1213,15 +1141,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UserRequest' + $ref: "#/components/schemas/UserRequest" responses: - '201': + "201": description: Created - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error /v1/users/{id}: get: @@ -1241,19 +1169,19 @@ paths: security: - basicAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/User' - '400': + $ref: "#/components/schemas/User" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error put: tags: @@ -1277,17 +1205,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UserRequest' + $ref: "#/components/schemas/UserRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error delete: tags: @@ -1306,15 +1234,15 @@ paths: security: - basicAuth: [] responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error /v1/jobs: get: @@ -1339,17 +1267,17 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Jobs' - '400': + $ref: "#/components/schemas/Jobs" + "400": description: Bad Request - '401': - description: Unauthenticated - '500': + "401": + description: Authorization Error + "500": description: Internal Server Error post: tags: @@ -1365,17 +1293,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/JobCreateRequest' + $ref: "#/components/schemas/JobCreateRequest" responses: - '201': + "201": description: Created - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '409': + "401": + description: Authorization Error + "409": description: Conflict - '500': + "500": description: Internal Server Error /v1/jobs/{id}: get: @@ -1395,19 +1323,19 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Success content: application/json: schema: - $ref: '#/components/schemas/Job' - '400': + $ref: "#/components/schemas/Job" + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '500': + "500": description: Internal Server Error put: tags: @@ -1431,19 +1359,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/JobUpdateRequest' + $ref: "#/components/schemas/JobUpdateRequest" responses: - '204': + "204": description: No Content - '400': + "400": description: Bad Request - '401': - description: Unauthenticated - '404': + "401": + description: Authorization Error + "404": description: Not Found - '409': + "409": description: Conflict - '500': + "500": description: Internal Server Error components: securitySchemes: @@ -1460,7 +1388,7 @@ components: properties: version: type: string - example: 24.06.24 + example: 2025.09.30 gitSha: type: string example: addf120b430021c36c232c99ef8d926aea2acd6b @@ -1579,14 +1507,26 @@ components: readOnly: true type: string enum: ["UNSPECIFIED", "PROCESSING", "READY", "FAILED"] + example: READY mediaItemType: readOnly: true type: string - enum: ["photo", "video"] + enum: ["UNKNOWN", "PHOTO", "VIDEO"] + example: PHOTO mediaItemCategory: readOnly: true type: string - enum: ["default", "screenshot", "panorama", "slow", "motion", "live"] + enum: + [ + "DEFAULT", + "SCREENSHOT", + "PANORAMA", + "SLOW", + "MOTION", + "LIVE", + "TIMELAPSE", + ] + example: SCREENSHOT width: readOnly: true type: integer @@ -1632,6 +1572,10 @@ components: readOnly: true type: string example: 1/20 + megapixels: + readOnly: true + type: string + example: 24 fps: readOnly: true type: string @@ -1717,7 +1661,7 @@ components: format: date-time example: "2022-09-14T16:58:28Z" coverMediaItem: - $ref: '#/components/schemas/MediaItem' + $ref: "#/components/schemas/MediaItem" Place: type: object properties: @@ -1754,32 +1698,7 @@ components: format: date-time example: "2022-09-14T16:58:28Z" coverMediaItem: - $ref: '#/components/schemas/MediaItem' - Thing: - type: object - properties: - id: - readOnly: true - type: string - format: uuid - example: 102dc522de-1669-48a4-8270-4950a0237599 - name: - type: string - example: Cars - coverMediaItemId: - type: string - format: uuid - example: 102dc522de-1669-48a4-8270-4950a0237599 - createdAt: - type: string - format: date-time - example: "2022-09-14T16:58:28Z" - updatedAt: - type: string - format: date-time - example: "2022-09-14T16:58:28Z" - coverMediaItem: - $ref: '#/components/schemas/MediaItem' + $ref: "#/components/schemas/MediaItem" PeopleRequest: type: object properties: @@ -1821,7 +1740,7 @@ components: format: date-time example: "2022-09-14T16:58:28Z" coverMediaItemFace: - $ref: '#/components/schemas/MediaItemFace' + $ref: "#/components/schemas/MediaItemFace" UserRequest: type: object properties: @@ -1851,6 +1770,9 @@ components: password: type: string example: somesecrethash + features: + type: string + example: { "key": "value" } createdAt: readOnly: true type: string @@ -1875,8 +1797,20 @@ components: format: uuid example: 102dc522de-1669-48a4-8270-4950a0237599 components: - type: string - example: metadata, faces + type: array + items: + type: string + enum: + [ + "METADATA", + "PREVIEW_THUMBNAIL", + "PLACES", + "CLASSIFICATION", + "FACES", + "OCR", + "SEARCH", + ] + example: ["METADATA", "PREVIEW_THUMBNAIL"] status: readOnly: true type: string @@ -1899,8 +1833,20 @@ components: type: object properties: components: - type: string - example: metadata,faces + type: array + items: + type: string + enum: + [ + "METADATA", + "PREVIEW_THUMBNAIL", + "PLACES", + "CLASSIFICATION", + "FACES", + "OCR", + "SEARCH", + ] + example: ["METADATA", "PREVIEW_THUMBNAIL"] JobUpdateRequest: type: object properties: @@ -1911,28 +1857,24 @@ components: MediaItems: type: array items: - $ref: '#/components/schemas/MediaItem' + $ref: "#/components/schemas/MediaItem" Places: type: array items: - $ref: '#/components/schemas/Place' - Things: - type: array - items: - $ref: '#/components/schemas/Thing' + $ref: "#/components/schemas/Place" People: type: array items: - $ref: '#/components/schemas/Person' + $ref: "#/components/schemas/Person" Albums: type: array items: - $ref: '#/components/schemas/Album' + $ref: "#/components/schemas/Album" Users: type: array items: - $ref: '#/components/schemas/User' + $ref: "#/components/schemas/User" Jobs: type: array items: - $ref: '#/components/schemas/Job' \ No newline at end of file + $ref: "#/components/schemas/Job" diff --git a/infra/deployments/docker-swarm/docker-compose.yaml b/infra/deployments/docker-swarm/docker-compose.yaml index 577afcd8..e29192ad 100644 --- a/infra/deployments/docker-swarm/docker-compose.yaml +++ b/infra/deployments/docker-swarm/docker-compose.yaml @@ -20,7 +20,7 @@ services: volumes: - ./storage:/storage:rw worker: - image: smritihq/worker:master + image: smritihq/pyworker:master ports: - '15002:15002' environment: diff --git a/ml/.gitignore b/ml/.gitignore index b380ceb1..5cf20828 100644 --- a/ml/.gitignore +++ b/ml/.gitignore @@ -1,4 +1,5 @@ *.pt +*.pth *.pdmodel *.pdiparams *.pdiparams.info @@ -55,4 +56,5 @@ Thumbs.db .idea/ *checkpoints* *.onnx -search/**/*.json \ No newline at end of file +search/**/*.json +**/result/*.jpg \ No newline at end of file diff --git a/ml/classification/README.md b/ml/classification/README.md deleted file mode 100644 index 10d3b354..00000000 --- a/ml/classification/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Smriti ML Classification -This is a reference for making ML models ready for inference. -The idea is for smriti to support multiple runtimes for different types of models. - -## Providers -Following types of providers are available for running Classification: -- [PyTorch](https://pytorch.org/) - -### PyTorch -Refer to [pytorch.py](pytorch.py) for creating a [TorchScript](https://pytorch.org/docs/stable/jit.html) Module using a [TorchVision](https://pytorch.org/vision/stable/index.html) model. - -#### Save TorchScript Module -``` -python3 pytorch.py save -``` - -#### Run Inference -``` -python3 pytorch.py run /path/to/example.jpg -``` - -### Tensorflow -Coming Soon! - -### ONNX -Coming Soon! diff --git a/ml/classification/pytorch.py b/ml/classification/pytorch.py deleted file mode 100644 index a5d60b30..00000000 --- a/ml/classification/pytorch.py +++ /dev/null @@ -1,57 +0,0 @@ -"""ML Classification TorchVision Model""" -import os -import sys - -from PIL import Image -import torch -from torchvision import transforms -from torchvision.models import get_model, get_model_weights - - -VERSION=os.getenv('VERSION', 'dev').replace('.', '') -FILE_NAME = f'classification_v{VERSION}.pt' -MODEL_NAME = 'efficientnet_v2_s' # can be any torchvision classification model - -class SmritiClassificationPyTorchModule(torch.nn.Module): - """EfficientNet TorchScript Module""" - def __init__(self, model, weights) -> None: - super(SmritiClassificationPyTorchModule, self).__init__() - self.model = model - self.model.eval() - self.transforms = weights.transforms() - self.categories = weights.meta['categories'] - - def forward(self, img_tensor, topk: int=5): - """Forward Pass""" - input_tensor = self.transforms(img_tensor) - output = self.model(input_tensor) - probabilities = torch.nn.functional.softmax(output, dim=1) - top_prob, top_class = torch.topk(probabilities, topk) - return dict({self.categories[top_class[0][idx].item()]: top_prob[0][idx].item() \ - for idx in range(0, int(topk))}) - -def script_and_save(): - """Initialize pytorch model with weights, script it and save the torchscript module""" - print('scripting and saving torchscript module') - scripted_module = torch.jit.script(SmritiClassificationPyTorchModule(get_model(MODEL_NAME, weights='DEFAULT'), get_model_weights(MODEL_NAME).DEFAULT)) - scripted_module.save(FILE_NAME) - -def load_and_run(sample): - """Loads the saved torchscript module and runs sample image""" - print('loading and running torchscript module') - model = torch.jit.load(FILE_NAME) - img = Image.open(sample) - transform = transforms.ToTensor() - input_tensor = transform(img).unsqueeze(0) - print(model(input_tensor)) - -if __name__ == '__main__': - args = sys.argv - if len(args) > 1: - if args[1] == 'save': - script_and_save() - exit(0) - if args[1] == 'run': - load_and_run(args[2]) - exit(0) - print('provide a valid arg: save OR run') diff --git a/ml/faces/README.md b/ml/faces/README.md index a6c540cc..d6121f8d 100644 --- a/ml/faces/README.md +++ b/ml/faces/README.md @@ -1,26 +1,26 @@ # Smriti ML Faces -This is a reference for making ML models ready for inference to detect faces in images. -The idea is for smriti to support multiple runtimes for different types of models. + +This is a reference for making ML models ready for inference to detect and recgonize faces in images. The idea is for +smriti to support multiple runtimes or libraries for different types of models. ## Providers + Following types of providers are available for running Face Detection: -- [PyTorch](https://pytorch.org/) -### PyTorch -Refer to [pytorch.py](pytorch.py) for downloading model files. +- [ONNX](https://onnxruntime.ai/) + +### ONNX + +Refer to [export_onnx.py](export_onnx.py) for downloading, exporting, saving and running the model. #### Save PyTorch Model Assets + ``` python3 pytorch.py save ``` #### Run Inference + ``` python3 pytorch.py run /path/to/example.jpg ``` - -### Tensorflow -Coming Soon! - -### ONNX -Coming Soon! diff --git a/ml/faces/example.jpg b/ml/faces/example.jpg index 11263f1b..d7d872dd 100644 Binary files a/ml/faces/example.jpg and b/ml/faces/example.jpg differ diff --git a/ml/faces/export_onnx.py b/ml/faces/export_onnx.py new file mode 100644 index 00000000..5e813ac4 --- /dev/null +++ b/ml/faces/export_onnx.py @@ -0,0 +1,134 @@ +"""ML Faces using ONNX model""" +import sys +import os +import urllib.request +import zipfile + +import cv2 +import numpy as np +import onnxruntime as ort + + +VERSION=os.getenv("VERSION", "dev").replace(".", "") +FILENAME_SUFFIX = f"_v{VERSION}" +DET_FILENAME = f"faces_det{FILENAME_SUFFIX}.onnx" +DET_MODELS_ZIPNAME = f"faces_det.zip" +REC_FILENAME = f"faces_rec{FILENAME_SUFFIX}.onnx" +REC_MODELS_ZIPNAME = f"faces_rec.zip" + +def export_and_save(): + """Download required models for detection and recognition, export and save for inference""" + print("downloading face detection model") + urllib.request.urlretrieve("https://www.dropbox.com/scl/fo/9y86d4qb2nmkdtlf61aw0/AMc1Qfk6TtkbZMkhj8bBOVI?rlkey=2ztw1h9cvj9bsfc2niu42znr5&st=8u3eyse2&dl=1", DET_MODELS_ZIPNAME) + with zipfile.ZipFile(DET_MODELS_ZIPNAME) as z: + z.extractall("faces_det") + os.remove(DET_MODELS_ZIPNAME) + print("exporting and saving face detection model") + print("all models are already exported in ONNX format!") + print("downloading face recognition model") + urllib.request.urlretrieve("https://www.dropbox.com/scl/fo/pubfb5wkiv5c5iucmlyou/ABKeExgXz5r-KMk5VUw2fPg?rlkey=djw1796psv1ncqun0kgswmkr9&st=ruowqv0e&dl=1", REC_MODELS_ZIPNAME) + with zipfile.ZipFile(REC_MODELS_ZIPNAME) as z: + z.extractall("faces_rec") + os.remove(REC_MODELS_ZIPNAME) + print("exporting and saving face recognition model") + print("all models are already exported in ONNX format!") + +def distance2bbox(points, distance): + x1, y1 = points[:, 0] - distance[:, 0], points[:, 1] - distance[:, 1] + x2, y2 = points[:, 0] + distance[:, 2], points[:, 1] + distance[:, 3] + return np.stack([x1, y1, x2, y2], axis=-1) + +def nms(dets, thresh=0.4): + x1 = dets[:, 0] + y1 = dets[:, 1] + x2 = dets[:, 2] + y2 = dets[:, 3] + scores = dets[:, 4] + areas = (x2 - x1 + 1) * (y2 - y1 + 1) + order = scores.argsort()[::-1] + keep = [] + while order.size > 0: + i = order[0] + keep.append(i) + xx1, yy1 = np.maximum(x1[i], x1[order[1:]]), np.maximum(y1[i], y1[order[1:]]) + xx2, yy2 = np.minimum(x2[i], x2[order[1:]]), np.minimum(y2[i], y2[order[1:]]) + w, h = np.maximum(0.0, xx2 - xx1 + 1), np.maximum(0.0, yy2 - yy1 + 1) + inter = w * h + ovr = inter / (areas[i] + areas[order[1:]] - inter) + order = order[np.where(ovr <= thresh)[0] + 1] + return keep + +def load_and_run(sample="example.jpg"): + """Loads the saved onnx models and runs sample image""" + # input + img = cv2.imread(sample) + input_size = (640, 640) + im_ratio = float(img.shape[0]) / img.shape[1] + model_ratio = float(input_size[1]) / input_size[0] + if im_ratio > model_ratio: + new_height = input_size[1] + new_width = int(new_height / im_ratio) + else: + new_width = input_size[0] + new_height = int(new_width * im_ratio) + det_scale = float(new_height) / img.shape[0] + resized_img = cv2.resize(img, (new_width, new_height)) + det_img = np.zeros( (input_size[1], input_size[0], 3), dtype=np.uint8 ) + det_img[:new_height, :new_width, :] = resized_img + blob = cv2.dnn.blobFromImage(det_img, 1.0/128.0, input_size, (127.5, 127.5, 127.5), swapRB=True) + # detection + for model in os.listdir("faces_det"): + session = ort.InferenceSession(f"faces_det/{model}", providers=["CPUExecutionProvider"]) + print(model, "inputs:", [(inp.name, inp.shape) for inp in session.get_inputs()]) + print(model, "outputs:", [(out.name, out.shape) for out in session.get_outputs()]) + input_name = session.get_inputs()[0].name + outputs = session.run(None, {input_name: blob}) + strides = [8, 16, 32] + for idx, stride in enumerate(strides): + scores = outputs[idx][0].reshape(-1) + bboxes = outputs[idx+3][0].reshape(-1, 4) * stride + h, w = blob.shape[2] // stride, blob.shape[3] // stride + grid = np.stack(np.mgrid[:h, :w][::-1], axis=-1).astype(np.float32) + anchors = (grid * stride).reshape(-1, 2) + anchors = np.repeat(anchors, 2, axis=0).reshape(-1, 2) + bboxes = distance2bbox(anchors, bboxes) / det_scale + keep_inds = scores >= 0.5 + scores, bboxes = scores[keep_inds], bboxes[keep_inds] + dets = np.hstack([bboxes, scores[:, None]]) + keep = nms(dets, thresh=0.4) + bboxes, scores = dets[keep, :4], dets[keep, 4] + img = cv2.imread(sample) + for idx, (box, score) in enumerate(zip(bboxes, scores)): + if score > 0.8: + x1, y1, x2, y2 = box.astype(int) + face = img[y1-1:y2-1, x1-1:x2-1] + cv2.imwrite(f"result/face_{model.replace('.onnx', '')}_{stride}_{idx}.jpg", face) + # recognition + for model in os.listdir("faces_rec"): + session = ort.InferenceSession(f"faces_rec/{model}", providers=["CPUExecutionProvider"]) + print(model, "inputs:", [(inp.name, inp.shape) for inp in session.get_inputs()]) + print(model, "outputs:", [(out.name, out.shape) for out in session.get_outputs()]) + input_name = session.get_inputs()[0].name + for img_path in os.listdir("result"): + img = cv2.imread(f"result/{img_path}") + img = cv2.resize(img, (112, 112)) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + img = np.transpose(img, (2, 0, 1)).astype(np.float32) + img = (img / 255.0 - 0.5) / 0.5 + img_np = np.expand_dims(img, axis=0) + outputs = session.run(None, {input_name: img_np}) + print(outputs[0].shape) + +if __name__ == "__main__": + args = sys.argv + if len(args) > 1: + if args[1] == "save": + export_and_save() + exit(0) + if args[1] == "run": + if len(args) == 3: + load_and_run(args[2]) + else: + load_and_run() + exit(0) + print("provide a valid arg: save OR run") diff --git a/ml/faces/pytorch.py b/ml/faces/pytorch.py deleted file mode 100644 index f10b5e19..00000000 --- a/ml/faces/pytorch.py +++ /dev/null @@ -1,42 +0,0 @@ -"""ML Faces PyTorch Model""" -import sys -import os -import urllib.request - -from PIL import Image -from facenet_pytorch import MTCNN, InceptionResnetV1 - - -def download_and_save(): - """Download models for detection, recognition and classification""" - print('downloading and saving pytorch models') - urllib.request.urlretrieve('https://github.com/timesler/facenet-pytorch/releases/download/v2.2.9/20180402-114759-vggface2.pt', '20180402-114759-vggface2.pt') - -def load_and_run(sample='example.jpg'): - """Loads the saved pytorch models and runs sample image""" - print('loading and running pytorch models') - os.environ['TORCH_HOME'] = os.getcwd() - try: - os.symlink(f'{os.getcwd()}', f'{os.getcwd()}/checkpoints') - except: - pass - det_model = MTCNN(keep_all=True) - rec_model = InceptionResnetV1(pretrained='vggface2', classify=False).eval() - faces, probs = det_model(Image.open(sample), return_prob=True) - result = rec_model(faces) - for res, prob in zip(result, probs): - print(res.shape, prob) - -if __name__ == '__main__': - args = sys.argv - if len(args) > 1: - if args[1] == 'save': - download_and_save() - exit(0) - if args[1] == 'run': - if len(args) == 3: - load_and_run(args[2]) - else: - load_and_run() - exit(0) - print('provide a valid arg: save OR run') diff --git a/ml/ocr/README.md b/ml/ocr/README.md index 5148f623..270cc5ca 100644 --- a/ml/ocr/README.md +++ b/ml/ocr/README.md @@ -1,26 +1,34 @@ # Smriti ML OCR + This is a reference for making ML models ready for inference to detect text in images. The idea is for smriti to support multiple runtimes for different types of models. ## Providers + Following types of providers are available for running OCR: + - [PaddlePaddle](https://github.com/PaddlePaddle/PaddleOCR) ### PaddlePaddle + Refer to [paddlepaddle.py](paddlepaddle.py) for downloading model files. #### Save PaddleOCR Model Assets + ``` python3 paddlepaddle.py save ``` #### Run Inference + ``` python3 paddlepaddle.py run /path/to/example.jpg ``` ### Tensorflow + Coming Soon! ### ONNX + Coming Soon! diff --git a/ml/search/README.md b/ml/search/README.md index 9cea4ed8..cbe2a268 100644 --- a/ml/search/README.md +++ b/ml/search/README.md @@ -1,26 +1,34 @@ # Smriti ML Search + This is a reference for making ML models ready for inference to search based on embeddings. The idea is for smriti to support multiple runtimes for different types of models. ## Providers + Following types of providers are available for running Search: + - [PyTorch](https://pytorch.org/) ### PyTorch + Refer to [pytorch.py](pytorch.py) for creating a [TorchScript](https://pytorch.org/docs/stable/jit.html) Module using a [HuggingFace](https://huggingface.co) model. #### Save TorchScript Module + ``` python3 pytorch.py save ``` #### Run Inference + ``` python3 pytorch.py run /path/to/example.jpg ``` ### Tensorflow + Coming Soon! ### ONNX + Coming Soon! diff --git a/protos/api.proto b/protos/api.proto index 58848ebd..6d03c452 100644 --- a/protos/api.proto +++ b/protos/api.proto @@ -5,112 +5,157 @@ import "google/protobuf/empty.proto"; option go_package = "api/"; message ConfigResponse { - bytes config = 1; + bytes config = 1; +} + +enum MediaItemComponent { + METADATA = 0; + PREVIEW_THUMBNAIL = 1; + PLACES = 2; + CLASSIFICATION = 3; + FACES = 4; + OCR = 5; + SEARCH = 6; +} + +enum MediaItemStatus { + UNSPECIFIED = 0; + PROCESSING = 1; + READY = 2; + FAILED = 3; +} + +enum MediaItemType { + UNKNOWN = 0; + PHOTO = 1; + VIDEO = 2; +} + +enum MediaItemCategory { + DEFAULT = 0; + SCREENSHOT = 1; + PANORAMA = 2; + SLOW = 3; + MOTION = 4; + LIVE = 5; + TIMELAPSE = 6; +} + +message MediaItemProcessResponse { + string id = 1; + string userId = 2; + string mediaItemId = 3; + repeated MediaItemComponent components = 4; + map payload = 5; } message MediaItemMetadataRequest { - string userId = 1; - string id = 2; - string status = 3; - optional string mimeType = 4; - string type = 5; - string category = 6; - optional int32 width = 7; - optional int32 height = 8; - optional string creationTime = 9; - optional string cameraMake = 10; - optional string cameraModel = 11; - optional string focalLength = 12; - optional string apertureFNumber = 13; - optional string isoEquivalent = 14; - optional string exposureTime = 15; - optional string fps = 16; - optional double latitude = 17; - optional double longitude = 18; - optional string exifData = 19; + string userId = 1; + string mediaItemId = 2; + MediaItemStatus status = 3; + optional string mimeType = 4; + MediaItemType type = 5; + MediaItemCategory category = 6; + optional int32 width = 7; + optional int32 height = 8; + optional string creationTime = 9; + optional string cameraMake = 10; + optional string cameraModel = 11; + optional string focalLength = 12; + optional string apertureFNumber = 13; + optional string isoEquivalent = 14; + optional string exposureTime = 15; + optional string megapixels = 16; + optional string fps = 17; + optional double latitude = 18; + optional double longitude = 19; + optional string exifData = 20; } message MediaItemPreviewThumbnailRequest { - string userId = 1; - string id = 2; - string status = 3; - optional string sourcePath = 4; - optional string previewPath = 5; - optional string thumbnailPath = 6; - optional string placeholder = 7; + string userId = 1; + string mediaItemId = 2; + MediaItemStatus status = 3; + optional string sourcePath = 4; + optional string previewPath = 5; + optional string thumbnailPath = 6; + optional string placeholder = 7; } message MediaItemPlaceRequest { - string userId = 1; - string id = 2; - optional string postcode = 3; - optional string country = 4; - optional string state = 5; - optional string city = 6; - optional string town = 7; -} - -message MediaItemThingRequest { - string userId = 1; - string id = 2; - string name = 3; + string userId = 1; + string mediaItemId = 2; + optional string postcode = 3; + optional string country = 4; + optional string locality = 5; // city, town, village, municipality + optional string area = 6; // suburb, road, neighbourhood } message MediaItemEmbedding { - repeated float embedding = 1; + repeated float embedding = 1; } message MediaItemFacesRequest { - string userId = 1; - string id = 2; - repeated MediaItemEmbedding embeddings = 3; - repeated string thumbnails = 4; + string userId = 1; + string mediaItemId = 2; + repeated MediaItemEmbedding embeddings = 3; + repeated string thumbnails = 4; } message MediaItemFinalResultRequest { - string userId = 1; - string id = 2; - string keywords = 3; - repeated MediaItemEmbedding embeddings = 4; + string id = 1; + string userId = 2; + string mediaItemId = 3; + string detectedText = 4; + string caption = 5; + repeated MediaItemEmbedding embeddings = 6; } message MediaItemFaceEmbeddingsRequest { - string userId = 1; + string userId = 1; } message MediaItemFaceEmbedding { - string id = 1; - string mediaItemId = 2; - string peopleId = 3; - MediaItemEmbedding embedding = 4; + string id = 1; + string mediaItemId = 2; + string peopleId = 3; + MediaItemEmbedding embedding = 4; } message MediaItemFaceEmbeddingsResponse { - repeated MediaItemFaceEmbedding mediaItemFaceEmbeddings = 1; + repeated MediaItemFaceEmbedding mediaItemFaceEmbeddings = 1; } -message GetUsersResponse { - repeated string users = 1; +message UsersResponse { + repeated string users = 1; } message MediaItemFacePeople { - map facePeople = 1; + map facePeople = 1; } message MediaItemPeopleRequest { - string userId = 1; - map mediaItemFacePeople = 2; + string userId = 1; + map mediaItemFacePeople = 2; } service API { - rpc GetWorkerConfig(google.protobuf.Empty) returns (ConfigResponse) {} - rpc GetMediaItemFaceEmbeddings(MediaItemFaceEmbeddingsRequest) returns (MediaItemFaceEmbeddingsResponse) {} - rpc GetUsers(google.protobuf.Empty) returns (GetUsersResponse) {} - rpc SaveMediaItemMetadata(MediaItemMetadataRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemPreviewThumbnail(MediaItemPreviewThumbnailRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemPlace(MediaItemPlaceRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemThing(MediaItemThingRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemFaces(MediaItemFacesRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemPeople(MediaItemPeopleRequest) returns (google.protobuf.Empty) {} - rpc SaveMediaItemFinalResult(MediaItemFinalResultRequest) returns (google.protobuf.Empty) {} + rpc GetWorkerConfig(google.protobuf.Empty) returns (ConfigResponse) {} + rpc GetMediaItemProcess(google.protobuf.Empty) + returns (MediaItemProcessResponse) {} + rpc GetMediaItemFaceEmbeddings(MediaItemFaceEmbeddingsRequest) + returns (MediaItemFaceEmbeddingsResponse) {} + rpc GetUsers(google.protobuf.Empty) returns (UsersResponse) {} + rpc SaveMediaItemMetadata(MediaItemMetadataRequest) + returns (google.protobuf.Empty) {} + rpc SaveMediaItemPreviewThumbnail(MediaItemPreviewThumbnailRequest) + returns (google.protobuf.Empty) {} + rpc SaveMediaItemPlace(MediaItemPlaceRequest) + returns (google.protobuf.Empty) {} + rpc SaveMediaItemFaces(MediaItemFacesRequest) + returns (google.protobuf.Empty) {} + rpc SaveMediaItemPeople(MediaItemPeopleRequest) + returns (google.protobuf.Empty) {} + rpc SaveMediaItemFinalResult(MediaItemFinalResultRequest) + returns (google.protobuf.Empty) {} } diff --git a/protos/worker.proto b/protos/worker.proto index 31171d05..b4e5d49e 100644 --- a/protos/worker.proto +++ b/protos/worker.proto @@ -2,37 +2,15 @@ syntax = "proto3"; option go_package = "worker/"; -enum MediaItemComponent { - METADATA = 0; - PREVIEW_THUMBNAIL = 1; - PLACES = 2; - CLASSIFICATION = 3; - FACES = 4; - OCR = 5; - SEARCH = 6; -} - -message MediaItemProcessRequest { - string userId = 1; - string id = 2; - string filePath = 3; - repeated MediaItemComponent components = 4; - map payload = 5; -} - -message MediaItemProcessResponse { - bool ok = 1; -} - message GenerateEmbeddingRequest { - string text = 1; + string text = 1; } message GenerateEmbeddingResponse { - repeated float embedding = 1; + repeated float embedding = 1; } service Worker { - rpc MediaItemProcess(MediaItemProcessRequest) returns (MediaItemProcessResponse) {} - rpc GenerateEmbedding(GenerateEmbeddingRequest) returns (GenerateEmbeddingResponse) {} + rpc GenerateEmbedding(GenerateEmbeddingRequest) + returns (GenerateEmbeddingResponse) {} } diff --git a/pyworker/.gitignore b/pyworker/.gitignore new file mode 100644 index 00000000..1f903673 --- /dev/null +++ b/pyworker/.gitignore @@ -0,0 +1,50 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +*.manifest +*.spec +pip-log.txt +pip-delete-this-directory.txt +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +Thumbs.db +.DS_Store +.idea/ +fly.toml \ No newline at end of file diff --git a/worker/.pylintrc b/pyworker/.pylintrc similarity index 100% rename from worker/.pylintrc rename to pyworker/.pylintrc diff --git a/pyworker/Dockerfile b/pyworker/Dockerfile new file mode 100644 index 00000000..63d140a8 --- /dev/null +++ b/pyworker/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.12-slim AS builder +RUN apt update && \ + apt install --no-install-recommends -y build-essential \ + curl gcc libssl-dev python3-opencv ffmpeg libmagickwand-dev \ + libimage-exiftool-perl exiftool libraw-dev +RUN curl --proto '=https' --tlsv1.3 -sSf https://sh.rustup.rs -o rustup-init.sh +RUN sh rustup-init.sh -y +ENV PATH="/root/.cargo/bin:${PATH}" +ADD requirements.txt /requirements.txt +RUN pip3 install --no-cache-dir -r requirements.txt -vvv +COPY src /app/src +EXPOSE 15002 +WORKDIR /app +CMD ["python3", "-m", "src.main"] \ No newline at end of file diff --git a/pyworker/Makefile b/pyworker/Makefile new file mode 100644 index 00000000..992f665a --- /dev/null +++ b/pyworker/Makefile @@ -0,0 +1,28 @@ +install: + @pip install -r requirements.txt + +run: + @python3 -m src.main + +test-install: + @pip install pytest pytest-cov requests-mock pytest-asyncio + +test: + @python3 -m pytest + +cover: + @coverage run --source src -m pytest + @coverage report --omit=src/protos/*,*__.py + @coverage xml --omit=src/protos/*,*__.py + +cover-html: cover + @coverage html --omit=src/protos/*,*__.py + +lint: + @pylint * + +proto: + @mkdir -p ../src/protos + @cp -r ../protos/* ../src/protos/ + @python3 -m grpc_tools.protoc -I../ --python_out=./ --pyi_out=./ --grpc_python_out=./ ../src/protos/*.proto + @rm -rf ../src \ No newline at end of file diff --git a/pyworker/README.md b/pyworker/README.md new file mode 100644 index 00000000..20f7e432 --- /dev/null +++ b/pyworker/README.md @@ -0,0 +1,3 @@ +# Smriti PyWorker + +Setup Guide is available [here](https://smriti.omkar.xyz/docs/dev-guide/environment#pyworker). diff --git a/worker/requirements.txt b/pyworker/requirements.txt similarity index 100% rename from worker/requirements.txt rename to pyworker/requirements.txt diff --git a/worker/src/components/__init__.py b/pyworker/src/components/__init__.py similarity index 100% rename from worker/src/components/__init__.py rename to pyworker/src/components/__init__.py diff --git a/worker/src/components/classification.py b/pyworker/src/components/classification.py similarity index 100% rename from worker/src/components/classification.py rename to pyworker/src/components/classification.py diff --git a/worker/src/components/component.py b/pyworker/src/components/component.py similarity index 100% rename from worker/src/components/component.py rename to pyworker/src/components/component.py diff --git a/worker/src/components/faces.py b/pyworker/src/components/faces.py similarity index 100% rename from worker/src/components/faces.py rename to pyworker/src/components/faces.py diff --git a/worker/src/components/finalize.py b/pyworker/src/components/finalize.py similarity index 100% rename from worker/src/components/finalize.py rename to pyworker/src/components/finalize.py diff --git a/worker/src/components/metadata.py b/pyworker/src/components/metadata.py similarity index 100% rename from worker/src/components/metadata.py rename to pyworker/src/components/metadata.py diff --git a/worker/src/components/ocr.py b/pyworker/src/components/ocr.py similarity index 100% rename from worker/src/components/ocr.py rename to pyworker/src/components/ocr.py diff --git a/worker/src/components/places.py b/pyworker/src/components/places.py similarity index 85% rename from worker/src/components/places.py rename to pyworker/src/components/places.py index 2d9fc9ef..b1a7531e 100644 --- a/worker/src/components/places.py +++ b/pyworker/src/components/places.py @@ -29,14 +29,12 @@ async def process(self, mediaitem_user_id: str, mediaitem_id: str, _: str, metad place_keywords = '' if result['postcode']: place_keywords += (result['postcode'].lower()+' ') - if result['city']: - place_keywords += (result['city'].lower()+' ') - if result['town']: - place_keywords += (result['town'].lower()+' ') - if result['state']: - place_keywords += (result['state'].lower()+' ') if result['country']: place_keywords += (result['country'].lower()+' ') + if result['locality']: + place_keywords += (result['locality'].lower()+' ') + if result['area']: + place_keywords += (result['area'].lower()+' ') place_keywords = place_keywords.strip() if 'keywords' not in metadata or metadata['keywords'] == '': metadata['keywords'] = place_keywords @@ -60,9 +58,8 @@ def _grpc_save_mediaitem_place(self, result: dict): id=result['id'], postcode=result['postcode'] if 'postcode' in result else None, country=result['country'] if 'country' in result else None, - state=result['state'] if 'state' in result else None, - city=result['city'] if 'city' in result else None, - town=result['town'] if 'town' in result else None, + locality=result['locality'] if 'locality' in result else None, + area=result['area'] if 'area' in result else None, ) _ = self.api_stub.SaveMediaItemPlace(request) except RpcError as rpc_exp: diff --git a/worker/src/components/preview_thumbnail.py b/pyworker/src/components/preview_thumbnail.py similarity index 100% rename from worker/src/components/preview_thumbnail.py rename to pyworker/src/components/preview_thumbnail.py diff --git a/worker/src/main.py b/pyworker/src/main.py similarity index 100% rename from worker/src/main.py rename to pyworker/src/main.py diff --git a/worker/src/protos/__init__.py b/pyworker/src/protos/__init__.py similarity index 100% rename from worker/src/protos/__init__.py rename to pyworker/src/protos/__init__.py diff --git a/worker/src/protos/api_pb2.py b/pyworker/src/protos/api_pb2.py similarity index 51% rename from worker/src/protos/api_pb2.py rename to pyworker/src/protos/api_pb2.py index 6bf46b69..0f90d7c0 100644 --- a/worker/src/protos/api_pb2.py +++ b/pyworker/src/protos/api_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: src/protos/api.proto -# Protobuf Python Version: 5.29.0 +# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 5, - 29, - 0, + 28, + 1, '', 'src/protos/api.proto' ) @@ -25,7 +25,7 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14src/protos/api.proto\x1a\x1bgoogle/protobuf/empty.proto\" \n\x0e\x43onfigResponse\x12\x0e\n\x06\x63onfig\x18\x01 \x01(\x0c\"\x84\x05\n\x18MediaItemMetadataRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x15\n\x08mimeType\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04type\x18\x05 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x06 \x01(\t\x12\x12\n\x05width\x18\x07 \x01(\x05H\x01\x88\x01\x01\x12\x13\n\x06height\x18\x08 \x01(\x05H\x02\x88\x01\x01\x12\x19\n\x0c\x63reationTime\x18\t \x01(\tH\x03\x88\x01\x01\x12\x17\n\ncameraMake\x18\n \x01(\tH\x04\x88\x01\x01\x12\x18\n\x0b\x63\x61meraModel\x18\x0b \x01(\tH\x05\x88\x01\x01\x12\x18\n\x0b\x66ocalLength\x18\x0c \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x61pertureFNumber\x18\r \x01(\tH\x07\x88\x01\x01\x12\x1a\n\risoEquivalent\x18\x0e \x01(\tH\x08\x88\x01\x01\x12\x19\n\x0c\x65xposureTime\x18\x0f \x01(\tH\t\x88\x01\x01\x12\x10\n\x03\x66ps\x18\x10 \x01(\tH\n\x88\x01\x01\x12\x15\n\x08latitude\x18\x11 \x01(\x01H\x0b\x88\x01\x01\x12\x16\n\tlongitude\x18\x12 \x01(\x01H\x0c\x88\x01\x01\x12\x15\n\x08\x65xifData\x18\x13 \x01(\tH\r\x88\x01\x01\x42\x0b\n\t_mimeTypeB\x08\n\x06_widthB\t\n\x07_heightB\x0f\n\r_creationTimeB\r\n\x0b_cameraMakeB\x0e\n\x0c_cameraModelB\x0e\n\x0c_focalLengthB\x12\n\x10_apertureFNumberB\x10\n\x0e_isoEquivalentB\x0f\n\r_exposureTimeB\x06\n\x04_fpsB\x0b\n\t_latitudeB\x0c\n\n_longitudeB\x0b\n\t_exifData\"\xf8\x01\n MediaItemPreviewThumbnailRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x17\n\nsourcePath\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bpreviewPath\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rthumbnailPath\x18\x06 \x01(\tH\x02\x88\x01\x01\x12\x18\n\x0bplaceholder\x18\x07 \x01(\tH\x03\x88\x01\x01\x42\r\n\x0b_sourcePathB\x0e\n\x0c_previewPathB\x10\n\x0e_thumbnailPathB\x0e\n\x0c_placeholder\"\xcf\x01\n\x15MediaItemPlaceRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x15\n\x08postcode\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x63ountry\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x12\n\x05state\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x11\n\x04\x63ity\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x11\n\x04town\x18\x07 \x01(\tH\x04\x88\x01\x01\x42\x0b\n\t_postcodeB\n\n\x08_countryB\x08\n\x06_stateB\x07\n\x05_cityB\x07\n\x05_town\"A\n\x15MediaItemThingRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\"\'\n\x12MediaItemEmbedding\x12\x11\n\tembedding\x18\x01 \x03(\x02\"p\n\x15MediaItemFacesRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\nembeddings\x18\x03 \x03(\x0b\x32\x13.MediaItemEmbedding\x12\x12\n\nthumbnails\x18\x04 \x03(\t\"t\n\x1bMediaItemFinalResultRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x10\n\x08keywords\x18\x03 \x01(\t\x12\'\n\nembeddings\x18\x04 \x03(\x0b\x32\x13.MediaItemEmbedding\"0\n\x1eMediaItemFaceEmbeddingsRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\"s\n\x16MediaItemFaceEmbedding\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bmediaItemId\x18\x02 \x01(\t\x12\x10\n\x08peopleId\x18\x03 \x01(\t\x12&\n\tembedding\x18\x04 \x01(\x0b\x32\x13.MediaItemEmbedding\"[\n\x1fMediaItemFaceEmbeddingsResponse\x12\x38\n\x17mediaItemFaceEmbeddings\x18\x01 \x03(\x0b\x32\x17.MediaItemFaceEmbedding\"!\n\x10GetUsersResponse\x12\r\n\x05users\x18\x01 \x03(\t\"\x82\x01\n\x13MediaItemFacePeople\x12\x38\n\nfacePeople\x18\x01 \x03(\x0b\x32$.MediaItemFacePeople.FacePeopleEntry\x1a\x31\n\x0f\x46\x61\x63\x65PeopleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc9\x01\n\x16MediaItemPeopleRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12M\n\x13mediaItemFacePeople\x18\x02 \x03(\x0b\x32\x30.MediaItemPeopleRequest.MediaItemFacePeopleEntry\x1aP\n\x18MediaItemFacePeopleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.MediaItemFacePeople:\x02\x38\x01\x32\x81\x06\n\x03\x41PI\x12<\n\x0fGetWorkerConfig\x12\x16.google.protobuf.Empty\x1a\x0f.ConfigResponse\"\x00\x12\x61\n\x1aGetMediaItemFaceEmbeddings\x12\x1f.MediaItemFaceEmbeddingsRequest\x1a .MediaItemFaceEmbeddingsResponse\"\x00\x12\x37\n\x08GetUsers\x12\x16.google.protobuf.Empty\x1a\x11.GetUsersResponse\"\x00\x12L\n\x15SaveMediaItemMetadata\x12\x19.MediaItemMetadataRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\\\n\x1dSaveMediaItemPreviewThumbnail\x12!.MediaItemPreviewThumbnailRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemPlace\x12\x16.MediaItemPlaceRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemThing\x12\x16.MediaItemThingRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemFaces\x12\x16.MediaItemFacesRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n\x13SaveMediaItemPeople\x12\x17.MediaItemPeopleRequest\x1a\x16.google.protobuf.Empty\"\x00\x12R\n\x18SaveMediaItemFinalResult\x12\x1c.MediaItemFinalResultRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04\x61pi/b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14src/protos/api.proto\x1a\x1bgoogle/protobuf/empty.proto\" \n\x0e\x43onfigResponse\x12\x0e\n\x06\x63onfig\x18\x01 \x01(\x0c\"\x84\x05\n\x18MediaItemMetadataRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x15\n\x08mimeType\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04type\x18\x05 \x01(\t\x12\x10\n\x08\x63\x61tegory\x18\x06 \x01(\t\x12\x12\n\x05width\x18\x07 \x01(\x05H\x01\x88\x01\x01\x12\x13\n\x06height\x18\x08 \x01(\x05H\x02\x88\x01\x01\x12\x19\n\x0c\x63reationTime\x18\t \x01(\tH\x03\x88\x01\x01\x12\x17\n\ncameraMake\x18\n \x01(\tH\x04\x88\x01\x01\x12\x18\n\x0b\x63\x61meraModel\x18\x0b \x01(\tH\x05\x88\x01\x01\x12\x18\n\x0b\x66ocalLength\x18\x0c \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x61pertureFNumber\x18\r \x01(\tH\x07\x88\x01\x01\x12\x1a\n\risoEquivalent\x18\x0e \x01(\tH\x08\x88\x01\x01\x12\x19\n\x0c\x65xposureTime\x18\x0f \x01(\tH\t\x88\x01\x01\x12\x10\n\x03\x66ps\x18\x10 \x01(\tH\n\x88\x01\x01\x12\x15\n\x08latitude\x18\x11 \x01(\x01H\x0b\x88\x01\x01\x12\x16\n\tlongitude\x18\x12 \x01(\x01H\x0c\x88\x01\x01\x12\x15\n\x08\x65xifData\x18\x13 \x01(\tH\r\x88\x01\x01\x42\x0b\n\t_mimeTypeB\x08\n\x06_widthB\t\n\x07_heightB\x0f\n\r_creationTimeB\r\n\x0b_cameraMakeB\x0e\n\x0c_cameraModelB\x0e\n\x0c_focalLengthB\x12\n\x10_apertureFNumberB\x10\n\x0e_isoEquivalentB\x0f\n\r_exposureTimeB\x06\n\x04_fpsB\x0b\n\t_latitudeB\x0c\n\n_longitudeB\x0b\n\t_exifData\"\xf8\x01\n MediaItemPreviewThumbnailRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x17\n\nsourcePath\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bpreviewPath\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rthumbnailPath\x18\x06 \x01(\tH\x02\x88\x01\x01\x12\x18\n\x0bplaceholder\x18\x07 \x01(\tH\x03\x88\x01\x01\x42\r\n\x0b_sourcePathB\x0e\n\x0c_previewPathB\x10\n\x0e_thumbnailPathB\x0e\n\x0c_placeholder\"\xb9\x01\n\x15MediaItemPlaceRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x15\n\x08postcode\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x63ountry\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08locality\x18\x05 \x01(\tH\x02\x88\x01\x01\x12\x11\n\x04\x61rea\x18\x06 \x01(\tH\x03\x88\x01\x01\x42\x0b\n\t_postcodeB\n\n\x08_countryB\x0b\n\t_localityB\x07\n\x05_area\"A\n\x15MediaItemThingRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\"\'\n\x12MediaItemEmbedding\x12\x11\n\tembedding\x18\x01 \x03(\x02\"p\n\x15MediaItemFacesRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\nembeddings\x18\x03 \x03(\x0b\x32\x13.MediaItemEmbedding\x12\x12\n\nthumbnails\x18\x04 \x03(\t\"t\n\x1bMediaItemFinalResultRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\x10\n\x08keywords\x18\x03 \x01(\t\x12\'\n\nembeddings\x18\x04 \x03(\x0b\x32\x13.MediaItemEmbedding\"0\n\x1eMediaItemFaceEmbeddingsRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\"s\n\x16MediaItemFaceEmbedding\x12\n\n\x02id\x18\x01 \x01(\t\x12\x13\n\x0bmediaItemId\x18\x02 \x01(\t\x12\x10\n\x08peopleId\x18\x03 \x01(\t\x12&\n\tembedding\x18\x04 \x01(\x0b\x32\x13.MediaItemEmbedding\"[\n\x1fMediaItemFaceEmbeddingsResponse\x12\x38\n\x17mediaItemFaceEmbeddings\x18\x01 \x03(\x0b\x32\x17.MediaItemFaceEmbedding\"!\n\x10GetUsersResponse\x12\r\n\x05users\x18\x01 \x03(\t\"\x82\x01\n\x13MediaItemFacePeople\x12\x38\n\nfacePeople\x18\x01 \x03(\x0b\x32$.MediaItemFacePeople.FacePeopleEntry\x1a\x31\n\x0f\x46\x61\x63\x65PeopleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc9\x01\n\x16MediaItemPeopleRequest\x12\x0e\n\x06userId\x18\x01 \x01(\t\x12M\n\x13mediaItemFacePeople\x18\x02 \x03(\x0b\x32\x30.MediaItemPeopleRequest.MediaItemFacePeopleEntry\x1aP\n\x18MediaItemFacePeopleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.MediaItemFacePeople:\x02\x38\x01\x32\x81\x06\n\x03\x41PI\x12<\n\x0fGetWorkerConfig\x12\x16.google.protobuf.Empty\x1a\x0f.ConfigResponse\"\x00\x12\x61\n\x1aGetMediaItemFaceEmbeddings\x12\x1f.MediaItemFaceEmbeddingsRequest\x1a .MediaItemFaceEmbeddingsResponse\"\x00\x12\x37\n\x08GetUsers\x12\x16.google.protobuf.Empty\x1a\x11.GetUsersResponse\"\x00\x12L\n\x15SaveMediaItemMetadata\x12\x19.MediaItemMetadataRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\\\n\x1dSaveMediaItemPreviewThumbnail\x12!.MediaItemPreviewThumbnailRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemPlace\x12\x16.MediaItemPlaceRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemThing\x12\x16.MediaItemThingRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x46\n\x12SaveMediaItemFaces\x12\x16.MediaItemFacesRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n\x13SaveMediaItemPeople\x12\x17.MediaItemPeopleRequest\x1a\x16.google.protobuf.Empty\"\x00\x12R\n\x18SaveMediaItemFinalResult\x12\x1c.MediaItemFinalResultRequest\x1a\x16.google.protobuf.Empty\"\x00\x42\x06Z\x04\x61pi/b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -44,31 +44,31 @@ _globals['_MEDIAITEMPREVIEWTHUMBNAILREQUEST']._serialized_start=735 _globals['_MEDIAITEMPREVIEWTHUMBNAILREQUEST']._serialized_end=983 _globals['_MEDIAITEMPLACEREQUEST']._serialized_start=986 - _globals['_MEDIAITEMPLACEREQUEST']._serialized_end=1193 - _globals['_MEDIAITEMTHINGREQUEST']._serialized_start=1195 - _globals['_MEDIAITEMTHINGREQUEST']._serialized_end=1260 - _globals['_MEDIAITEMEMBEDDING']._serialized_start=1262 - _globals['_MEDIAITEMEMBEDDING']._serialized_end=1301 - _globals['_MEDIAITEMFACESREQUEST']._serialized_start=1303 - _globals['_MEDIAITEMFACESREQUEST']._serialized_end=1415 - _globals['_MEDIAITEMFINALRESULTREQUEST']._serialized_start=1417 - _globals['_MEDIAITEMFINALRESULTREQUEST']._serialized_end=1533 - _globals['_MEDIAITEMFACEEMBEDDINGSREQUEST']._serialized_start=1535 - _globals['_MEDIAITEMFACEEMBEDDINGSREQUEST']._serialized_end=1583 - _globals['_MEDIAITEMFACEEMBEDDING']._serialized_start=1585 - _globals['_MEDIAITEMFACEEMBEDDING']._serialized_end=1700 - _globals['_MEDIAITEMFACEEMBEDDINGSRESPONSE']._serialized_start=1702 - _globals['_MEDIAITEMFACEEMBEDDINGSRESPONSE']._serialized_end=1793 - _globals['_GETUSERSRESPONSE']._serialized_start=1795 - _globals['_GETUSERSRESPONSE']._serialized_end=1828 - _globals['_MEDIAITEMFACEPEOPLE']._serialized_start=1831 - _globals['_MEDIAITEMFACEPEOPLE']._serialized_end=1961 - _globals['_MEDIAITEMFACEPEOPLE_FACEPEOPLEENTRY']._serialized_start=1912 - _globals['_MEDIAITEMFACEPEOPLE_FACEPEOPLEENTRY']._serialized_end=1961 - _globals['_MEDIAITEMPEOPLEREQUEST']._serialized_start=1964 - _globals['_MEDIAITEMPEOPLEREQUEST']._serialized_end=2165 - _globals['_MEDIAITEMPEOPLEREQUEST_MEDIAITEMFACEPEOPLEENTRY']._serialized_start=2085 - _globals['_MEDIAITEMPEOPLEREQUEST_MEDIAITEMFACEPEOPLEENTRY']._serialized_end=2165 - _globals['_API']._serialized_start=2168 - _globals['_API']._serialized_end=2937 + _globals['_MEDIAITEMPLACEREQUEST']._serialized_end=1171 + _globals['_MEDIAITEMTHINGREQUEST']._serialized_start=1173 + _globals['_MEDIAITEMTHINGREQUEST']._serialized_end=1238 + _globals['_MEDIAITEMEMBEDDING']._serialized_start=1240 + _globals['_MEDIAITEMEMBEDDING']._serialized_end=1279 + _globals['_MEDIAITEMFACESREQUEST']._serialized_start=1281 + _globals['_MEDIAITEMFACESREQUEST']._serialized_end=1393 + _globals['_MEDIAITEMFINALRESULTREQUEST']._serialized_start=1395 + _globals['_MEDIAITEMFINALRESULTREQUEST']._serialized_end=1511 + _globals['_MEDIAITEMFACEEMBEDDINGSREQUEST']._serialized_start=1513 + _globals['_MEDIAITEMFACEEMBEDDINGSREQUEST']._serialized_end=1561 + _globals['_MEDIAITEMFACEEMBEDDING']._serialized_start=1563 + _globals['_MEDIAITEMFACEEMBEDDING']._serialized_end=1678 + _globals['_MEDIAITEMFACEEMBEDDINGSRESPONSE']._serialized_start=1680 + _globals['_MEDIAITEMFACEEMBEDDINGSRESPONSE']._serialized_end=1771 + _globals['_GETUSERSRESPONSE']._serialized_start=1773 + _globals['_GETUSERSRESPONSE']._serialized_end=1806 + _globals['_MEDIAITEMFACEPEOPLE']._serialized_start=1809 + _globals['_MEDIAITEMFACEPEOPLE']._serialized_end=1939 + _globals['_MEDIAITEMFACEPEOPLE_FACEPEOPLEENTRY']._serialized_start=1890 + _globals['_MEDIAITEMFACEPEOPLE_FACEPEOPLEENTRY']._serialized_end=1939 + _globals['_MEDIAITEMPEOPLEREQUEST']._serialized_start=1942 + _globals['_MEDIAITEMPEOPLEREQUEST']._serialized_end=2143 + _globals['_MEDIAITEMPEOPLEREQUEST_MEDIAITEMFACEPEOPLEENTRY']._serialized_start=2063 + _globals['_MEDIAITEMPEOPLEREQUEST_MEDIAITEMFACEPEOPLEENTRY']._serialized_end=2143 + _globals['_API']._serialized_start=2146 + _globals['_API']._serialized_end=2915 # @@protoc_insertion_point(module_scope) diff --git a/worker/src/protos/api_pb2.pyi b/pyworker/src/protos/api_pb2.pyi similarity index 96% rename from worker/src/protos/api_pb2.pyi rename to pyworker/src/protos/api_pb2.pyi index da0a0ba0..52c5cf49 100644 --- a/worker/src/protos/api_pb2.pyi +++ b/pyworker/src/protos/api_pb2.pyi @@ -73,22 +73,20 @@ class MediaItemPreviewThumbnailRequest(_message.Message): def __init__(self, userId: _Optional[str] = ..., id: _Optional[str] = ..., status: _Optional[str] = ..., sourcePath: _Optional[str] = ..., previewPath: _Optional[str] = ..., thumbnailPath: _Optional[str] = ..., placeholder: _Optional[str] = ...) -> None: ... class MediaItemPlaceRequest(_message.Message): - __slots__ = ("userId", "id", "postcode", "country", "state", "city", "town") + __slots__ = ("userId", "id", "postcode", "country", "locality", "area") USERID_FIELD_NUMBER: _ClassVar[int] ID_FIELD_NUMBER: _ClassVar[int] POSTCODE_FIELD_NUMBER: _ClassVar[int] COUNTRY_FIELD_NUMBER: _ClassVar[int] - STATE_FIELD_NUMBER: _ClassVar[int] - CITY_FIELD_NUMBER: _ClassVar[int] - TOWN_FIELD_NUMBER: _ClassVar[int] + LOCALITY_FIELD_NUMBER: _ClassVar[int] + AREA_FIELD_NUMBER: _ClassVar[int] userId: str id: str postcode: str country: str - state: str - city: str - town: str - def __init__(self, userId: _Optional[str] = ..., id: _Optional[str] = ..., postcode: _Optional[str] = ..., country: _Optional[str] = ..., state: _Optional[str] = ..., city: _Optional[str] = ..., town: _Optional[str] = ...) -> None: ... + locality: str + area: str + def __init__(self, userId: _Optional[str] = ..., id: _Optional[str] = ..., postcode: _Optional[str] = ..., country: _Optional[str] = ..., locality: _Optional[str] = ..., area: _Optional[str] = ...) -> None: ... class MediaItemThingRequest(_message.Message): __slots__ = ("userId", "id", "name") diff --git a/worker/src/protos/api_pb2_grpc.py b/pyworker/src/protos/api_pb2_grpc.py similarity index 99% rename from worker/src/protos/api_pb2_grpc.py rename to pyworker/src/protos/api_pb2_grpc.py index 7f86b3d5..73bd816f 100644 --- a/worker/src/protos/api_pb2_grpc.py +++ b/pyworker/src/protos/api_pb2_grpc.py @@ -6,7 +6,7 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 from src.protos import api_pb2 as src_dot_protos_dot_api__pb2 -GRPC_GENERATED_VERSION = '1.70.0' +GRPC_GENERATED_VERSION = '1.68.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False diff --git a/worker/src/protos/worker_pb2.py b/pyworker/src/protos/worker_pb2.py similarity index 98% rename from worker/src/protos/worker_pb2.py rename to pyworker/src/protos/worker_pb2.py index c42ef0e8..2d8c97b5 100644 --- a/worker/src/protos/worker_pb2.py +++ b/pyworker/src/protos/worker_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: src/protos/worker.proto -# Protobuf Python Version: 5.29.0 +# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -12,8 +12,8 @@ _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, 5, - 29, - 0, + 28, + 1, '', 'src/protos/worker.proto' ) diff --git a/worker/src/protos/worker_pb2.pyi b/pyworker/src/protos/worker_pb2.pyi similarity index 100% rename from worker/src/protos/worker_pb2.pyi rename to pyworker/src/protos/worker_pb2.pyi diff --git a/worker/src/protos/worker_pb2_grpc.py b/pyworker/src/protos/worker_pb2_grpc.py similarity index 99% rename from worker/src/protos/worker_pb2_grpc.py rename to pyworker/src/protos/worker_pb2_grpc.py index 99c7266c..63b27344 100644 --- a/worker/src/protos/worker_pb2_grpc.py +++ b/pyworker/src/protos/worker_pb2_grpc.py @@ -5,7 +5,7 @@ from src.protos import worker_pb2 as src_dot_protos_dot_worker__pb2 -GRPC_GENERATED_VERSION = '1.70.0' +GRPC_GENERATED_VERSION = '1.68.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False diff --git a/worker/src/providers/__init__.py b/pyworker/src/providers/__init__.py similarity index 100% rename from worker/src/providers/__init__.py rename to pyworker/src/providers/__init__.py diff --git a/worker/src/providers/classification/__init__.py b/pyworker/src/providers/classification/__init__.py similarity index 100% rename from worker/src/providers/classification/__init__.py rename to pyworker/src/providers/classification/__init__.py diff --git a/worker/src/providers/classification/pytorch.py b/pyworker/src/providers/classification/pytorch.py similarity index 100% rename from worker/src/providers/classification/pytorch.py rename to pyworker/src/providers/classification/pytorch.py diff --git a/worker/src/providers/classification/utils.py b/pyworker/src/providers/classification/utils.py similarity index 100% rename from worker/src/providers/classification/utils.py rename to pyworker/src/providers/classification/utils.py diff --git a/worker/src/providers/faces/__init__.py b/pyworker/src/providers/faces/__init__.py similarity index 100% rename from worker/src/providers/faces/__init__.py rename to pyworker/src/providers/faces/__init__.py diff --git a/worker/src/providers/faces/pytorch.py b/pyworker/src/providers/faces/pytorch.py similarity index 100% rename from worker/src/providers/faces/pytorch.py rename to pyworker/src/providers/faces/pytorch.py diff --git a/worker/src/providers/faces/utils.py b/pyworker/src/providers/faces/utils.py similarity index 100% rename from worker/src/providers/faces/utils.py rename to pyworker/src/providers/faces/utils.py diff --git a/worker/src/providers/ocr/__init__.py b/pyworker/src/providers/ocr/__init__.py similarity index 100% rename from worker/src/providers/ocr/__init__.py rename to pyworker/src/providers/ocr/__init__.py diff --git a/worker/src/providers/ocr/paddlepaddle.py b/pyworker/src/providers/ocr/paddlepaddle.py similarity index 100% rename from worker/src/providers/ocr/paddlepaddle.py rename to pyworker/src/providers/ocr/paddlepaddle.py diff --git a/worker/src/providers/ocr/utils.py b/pyworker/src/providers/ocr/utils.py similarity index 100% rename from worker/src/providers/ocr/utils.py rename to pyworker/src/providers/ocr/utils.py diff --git a/worker/src/providers/places/__init__.py b/pyworker/src/providers/places/__init__.py similarity index 100% rename from worker/src/providers/places/__init__.py rename to pyworker/src/providers/places/__init__.py diff --git a/worker/src/providers/places/openstreetmap.py b/pyworker/src/providers/places/openstreetmap.py similarity index 83% rename from worker/src/providers/places/openstreetmap.py rename to pyworker/src/providers/places/openstreetmap.py index 1cf64576..76704276 100644 --- a/worker/src/providers/places/openstreetmap.py +++ b/pyworker/src/providers/places/openstreetmap.py @@ -25,8 +25,7 @@ def reverse_geocode(self, mediaitem_user_id: str, mediaitem_id: str, coordinates if 'address' not in body: return None if 'address' in body and ('postcode' not in body['address'] or \ - 'country' not in body['address'] or \ - 'state' not in body['address']): + 'country' not in body['address']): return None address = body['address'] @@ -35,7 +34,6 @@ def reverse_geocode(self, mediaitem_user_id: str, mediaitem_id: str, coordinates 'id': mediaitem_id, 'postcode': getval_from_dict(address, ['postcode']), 'country': getval_from_dict(address, ['country']), - 'state': getval_from_dict(address, ['state']), - 'city': getval_from_dict(address, ['city', 'county']), - 'town': getval_from_dict(address, ['town']), + 'locality': getval_from_dict(address, ['village', 'town', 'city', 'municipality', 'county']), + 'area': getval_from_dict(address, ['suburb', 'quarter', 'neighbourhood', 'road']), }) diff --git a/worker/src/providers/places/utils.py b/pyworker/src/providers/places/utils.py similarity index 100% rename from worker/src/providers/places/utils.py rename to pyworker/src/providers/places/utils.py diff --git a/worker/src/providers/search/__init__.py b/pyworker/src/providers/search/__init__.py similarity index 100% rename from worker/src/providers/search/__init__.py rename to pyworker/src/providers/search/__init__.py diff --git a/worker/src/providers/search/pytorch.py b/pyworker/src/providers/search/pytorch.py similarity index 100% rename from worker/src/providers/search/pytorch.py rename to pyworker/src/providers/search/pytorch.py diff --git a/worker/src/providers/search/utils.py b/pyworker/src/providers/search/utils.py similarity index 100% rename from worker/src/providers/search/utils.py rename to pyworker/src/providers/search/utils.py diff --git a/worker/src/utils.py b/pyworker/src/utils.py similarity index 100% rename from worker/src/utils.py rename to pyworker/src/utils.py diff --git a/worker/tests/components/test_classification.py b/pyworker/tests/components/test_classification.py similarity index 100% rename from worker/tests/components/test_classification.py rename to pyworker/tests/components/test_classification.py diff --git a/worker/tests/components/test_component.py b/pyworker/tests/components/test_component.py similarity index 100% rename from worker/tests/components/test_component.py rename to pyworker/tests/components/test_component.py diff --git a/worker/tests/components/test_faces.py b/pyworker/tests/components/test_faces.py similarity index 100% rename from worker/tests/components/test_faces.py rename to pyworker/tests/components/test_faces.py diff --git a/worker/tests/components/test_finalize.py b/pyworker/tests/components/test_finalize.py similarity index 100% rename from worker/tests/components/test_finalize.py rename to pyworker/tests/components/test_finalize.py diff --git a/worker/tests/components/test_metadata.py b/pyworker/tests/components/test_metadata.py similarity index 100% rename from worker/tests/components/test_metadata.py rename to pyworker/tests/components/test_metadata.py diff --git a/worker/tests/components/test_ocr.py b/pyworker/tests/components/test_ocr.py similarity index 100% rename from worker/tests/components/test_ocr.py rename to pyworker/tests/components/test_ocr.py diff --git a/worker/tests/components/test_places.py b/pyworker/tests/components/test_places.py similarity index 75% rename from worker/tests/components/test_places.py rename to pyworker/tests/components/test_places.py index 5a281075..1b014342 100644 --- a/worker/tests/components/test_places.py +++ b/pyworker/tests/components/test_places.py @@ -12,7 +12,7 @@ API_URL = 'https://nominatim.openstreetmap.org/reverse.php?zoom=18&format=jsonv2&lat={lat}&lon={lon}' -COMMON_RESULT = {'city': 'Dombivli', 'town': 'Dombivli', 'state': 'Maharashtra', 'postcode': '421201', 'country': 'India'} +COMMON_RESULT = {'locality': 'Dombivli', 'area': 'Ayre Road', 'postcode': '421201', 'country': 'India'} @mock.patch('src.providers.places.OpenStreetMap.reverse_geocode', return_value=COMMON_RESULT) @mock.patch('src.components.Places._grpc_save_mediaitem_place', return_value=None) @@ -20,7 +20,7 @@ async def test_places_process_success(_, __): result = await Places(None, 'openstreetmap').process('mediaitem_user_id', 'mediaitem_id', None, {'latitude': 19.2195856, 'longitude': 73.1056888}) - assert result == {'keywords': '421201 dombivli dombivli maharashtra india', 'latitude': 19.2195856, 'longitude': 73.1056888} + assert result == {'keywords': '421201 india dombivli ayre road', 'latitude': 19.2195856, 'longitude': 73.1056888} @mock.patch('src.providers.places.OpenStreetMap.reverse_geocode', return_value=COMMON_RESULT) @mock.patch('src.components.Places._grpc_save_mediaitem_place', return_value=None) @@ -28,7 +28,7 @@ async def test_places_process_success(_, __): async def test_places_process_success_with_keywords(_, __): result = await Places(None, 'openstreetmap').process('mediaitem_user_id', 'mediaitem_id', None, {'latitude': 19.2195856, 'longitude': 73.1056888, 'keywords': 'exists'}) - assert result == {'keywords': 'exists 421201 dombivli maharashtra india', 'latitude': 19.2195856, 'longitude': 73.1056888} + assert result == {'keywords': 'exists 421201 india dombivli ayre road', 'latitude': 19.2195856, 'longitude': 73.1056888} @pytest.mark.asyncio async def test_places_process_success_no_metadata(): @@ -43,14 +43,12 @@ async def test_places_process_failed_process_exception(_): {'latitude': 19.2195856, 'longitude': 73.1056888}) assert result == {'latitude': 19.2195856, 'longitude': 73.1056888} +@mock.patch('src.providers.places.OpenStreetMap.reverse_geocode', return_value=COMMON_RESULT) @pytest.mark.asyncio -async def test_places_process_grpc_exception(requests_mock): - requests_mock.get(url=API_URL.format(lat=19.2195856, lon=73.1056888), json={ - 'address': {'city': 'Dombivali', 'state': 'Maharashtra', 'postcode': '421201', 'country': 'India'} - }) +async def test_places_process_grpc_exception(_): grpc_mock = mock.MagicMock() grpc_mock.side_effect = grpc.RpcError(Exception('some error')) with mock.patch('src.protos.API.SaveMediaItemPlace', grpc_mock): result = await Places(APIStub(channel=grpc.insecure_channel('')), 'openstreetmap').process('mediaitem_user_id', 'mediaitem_id', None, {'latitude': 19.2195856, 'longitude': 73.1056888}) - assert result == {'keywords': '421201 dombivali maharashtra india', 'latitude': 19.2195856, 'longitude': 73.1056888} \ No newline at end of file + assert result == {'keywords': '421201 india dombivli ayre road', 'latitude': 19.2195856, 'longitude': 73.1056888} \ No newline at end of file diff --git a/worker/tests/components/test_preview_thumbnail.py b/pyworker/tests/components/test_preview_thumbnail.py similarity index 100% rename from worker/tests/components/test_preview_thumbnail.py rename to pyworker/tests/components/test_preview_thumbnail.py diff --git a/worker/tests/providers/classification/test_classification_pytorch.py b/pyworker/tests/providers/classification/test_classification_pytorch.py similarity index 100% rename from worker/tests/providers/classification/test_classification_pytorch.py rename to pyworker/tests/providers/classification/test_classification_pytorch.py diff --git a/worker/tests/providers/classification/test_classification_utils.py b/pyworker/tests/providers/classification/test_classification_utils.py similarity index 100% rename from worker/tests/providers/classification/test_classification_utils.py rename to pyworker/tests/providers/classification/test_classification_utils.py diff --git a/worker/tests/providers/faces/test_faces_pytorch.py b/pyworker/tests/providers/faces/test_faces_pytorch.py similarity index 100% rename from worker/tests/providers/faces/test_faces_pytorch.py rename to pyworker/tests/providers/faces/test_faces_pytorch.py diff --git a/worker/tests/providers/faces/test_faces_utils.py b/pyworker/tests/providers/faces/test_faces_utils.py similarity index 100% rename from worker/tests/providers/faces/test_faces_utils.py rename to pyworker/tests/providers/faces/test_faces_utils.py diff --git a/worker/tests/providers/ocr/test_ocr_utils.py b/pyworker/tests/providers/ocr/test_ocr_utils.py similarity index 100% rename from worker/tests/providers/ocr/test_ocr_utils.py rename to pyworker/tests/providers/ocr/test_ocr_utils.py diff --git a/worker/tests/providers/ocr/test_paddle.py b/pyworker/tests/providers/ocr/test_paddle.py similarity index 100% rename from worker/tests/providers/ocr/test_paddle.py rename to pyworker/tests/providers/ocr/test_paddle.py diff --git a/worker/tests/providers/places/test_openstreetmap.py b/pyworker/tests/providers/places/test_openstreetmap.py similarity index 90% rename from worker/tests/providers/places/test_openstreetmap.py rename to pyworker/tests/providers/places/test_openstreetmap.py index cbf88d70..ea1244e9 100644 --- a/worker/tests/providers/places/test_openstreetmap.py +++ b/pyworker/tests/providers/places/test_openstreetmap.py @@ -10,12 +10,12 @@ def test_openstreetmap_success(requests_mock): requests_mock.get(url=API_URL.format(lat=19.2195856, lon=73.1056888), json={ - 'address': {'city': 'Dombivali', 'state': 'Maharashtra', 'postcode': '421201', 'country': 'India'} + 'address': {'city': 'Dombivli', 'road': 'Ayre Road', 'postcode': '421201', 'country': 'India'} }) result = init_places('openstreetmap').reverse_geocode('mediaitem_user_id', 'mediaitem_id', [19.2195856, 73.1056888]) assert result == {'userId': 'mediaitem_user_id', 'id': 'mediaitem_id', 'postcode': '421201', 'country': 'India', - 'state': 'Maharashtra', 'city': 'Dombivali', 'town': None} + 'locality': 'Dombivli', 'area': 'Ayre Road'} def test_openstreetmap_failed_empty_response(requests_mock): requests_mock.get(url=API_URL.format( diff --git a/worker/tests/providers/places/test_places_utils.py b/pyworker/tests/providers/places/test_places_utils.py similarity index 100% rename from worker/tests/providers/places/test_places_utils.py rename to pyworker/tests/providers/places/test_places_utils.py diff --git a/worker/tests/providers/search/test_search_pytorch.py b/pyworker/tests/providers/search/test_search_pytorch.py similarity index 100% rename from worker/tests/providers/search/test_search_pytorch.py rename to pyworker/tests/providers/search/test_search_pytorch.py diff --git a/worker/tests/providers/search/test_search_utils.py b/pyworker/tests/providers/search/test_search_utils.py similarity index 100% rename from worker/tests/providers/search/test_search_utils.py rename to pyworker/tests/providers/search/test_search_utils.py diff --git a/worker/tests/test_utils.py b/pyworker/tests/test_utils.py similarity index 100% rename from worker/tests/test_utils.py rename to pyworker/tests/test_utils.py diff --git a/tests/Makefile b/tests/Makefile index 53e95c96..f0a91b00 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -12,7 +12,7 @@ test-auth: behave -i auth.feature test-mediaitems: @echo "testing feature: mediaitems" - behave -i mediaitems.feature + behave -i mediaitems.feature --tags=~video test-albums: @echo "testing feature: albums" behave -i albums.feature @@ -22,9 +22,6 @@ test-library: test-places: @echo "testing feature: places" behave -i places.feature -test-things: - @echo "testing feature: things" - behave -i things.feature test-people: @echo "testing feature: people" behave -i people.feature @@ -49,3 +46,5 @@ clear: @sudo rm -rf ../storage/previews/* @sudo rm -rf ../storage/thumbnails/* @sudo find ../storage/* -maxdepth 0 ! -name 'originals' ! -name 'previews' ! -name 'thumbnails' -exec rm -rf {} + +help: + @grep -E '^[a-zA-Z0-9_.-]+:' Makefile | cut -d: -f1 | sort -u diff --git a/tests/features/albums.feature b/tests/features/albums.feature index 2cc0a886..66f5e07b 100644 --- a/tests/features/albums.feature +++ b/tests/features/albums.feature @@ -4,7 +4,7 @@ Feature: Albums Given a user default is created if does not exist When user default logs in Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds + When upload default PHOTO mediaitem with auth if does not exist and wait 5 seconds Then mediaitem is uploaded or exists Scenario: Validate Create Album diff --git a/tests/features/environment.py b/tests/features/environment.py index b9df28b7..124ef732 100644 --- a/tests/features/environment.py +++ b/tests/features/environment.py @@ -2,10 +2,8 @@ import time -ALL_TABLES = ['thing_mediaitems', 'place_mediaitems', - 'people_mediaitems', 'album_mediaitems', - 'things', 'places', 'people', 'albums', - 'mediaitem_embeddings', 'mediaitem_faces', 'jobs', 'mediaitems', 'users'] +ALL_TABLES = ['queue', 'place_mediaitems', 'people_mediaitems', 'album_mediaitems', 'places', 'people', + 'albums', 'mediaitem_embeddings', 'mediaitem_faces', 'jobs', 'mediaitems', 'users'] def before_feature(context, feature): cleanup_tables() diff --git a/tests/features/library.feature b/tests/features/library.feature index b7a31019..ee3d7883 100644 --- a/tests/features/library.feature +++ b/tests/features/library.feature @@ -4,7 +4,7 @@ Feature: Library Given a user default is created if does not exist When user default logs in Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds + When upload default PHOTO mediaitem with auth if does not exist and wait 5 seconds Then mediaitem is uploaded or exists When get mediaitem with auth Then mediaitem is present diff --git a/tests/features/mediaitems.feature b/tests/features/mediaitems.feature index 0e61f3a5..edf2f747 100644 --- a/tests/features/mediaitems.feature +++ b/tests/features/mediaitems.feature @@ -7,9 +7,9 @@ Feature: MediaItems Scenario: Validate Create Photo MediaItem Given there are no mediaitems - When upload default photo mediaitem without auth and wait 0 seconds + When upload default PHOTO mediaitem without auth and wait 0 seconds Then auth error is found - When upload default photo mediaitem with auth and wait 5 seconds + When upload default PHOTO mediaitem with auth and wait 5 seconds Then mediaitem is uploaded When get mediaitem without auth Then auth error is found @@ -37,7 +37,7 @@ Feature: MediaItems Scenario: Validate Duplicate Photo MediaItem Given a mediaitem exists - When upload default photo mediaitem with auth and wait 0 seconds + When upload default PHOTO mediaitem with auth and wait 0 seconds Then mediaitem already exists Scenario: Validate Delete Photo MediaItem @@ -55,11 +55,12 @@ Feature: MediaItems When get all mediaitems with auth Then mediaitem is not present in list + @video Scenario: Validate Create Video MediaItem Given there are no mediaitems - When upload default video mediaitem without auth and wait 0 seconds + When upload default VIDEO mediaitem without auth and wait 0 seconds Then auth error is found - When upload default video mediaitem with auth and wait 30 seconds + When upload default VIDEO mediaitem with auth and wait 30 seconds Then mediaitem is uploaded When get mediaitem without auth Then auth error is found @@ -70,6 +71,7 @@ Feature: MediaItems When get all mediaitems with auth Then mediaitem is present in list + @video Scenario: Validate Delete Video MediaItem Given a mediaitem exists When delete mediaitem without auth diff --git a/tests/features/places.feature b/tests/features/places.feature index 6e736a78..a7a14696 100644 --- a/tests/features/places.feature +++ b/tests/features/places.feature @@ -4,7 +4,7 @@ Feature: Places Given a user default is created if does not exist When user default logs in Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds + When upload default PHOTO mediaitem with auth if does not exist and wait 5 seconds Then mediaitem is uploaded or exists Scenario: Validate Places diff --git a/tests/features/search.feature b/tests/features/search.feature index c5056d38..029c6d2e 100644 --- a/tests/features/search.feature +++ b/tests/features/search.feature @@ -4,7 +4,7 @@ Feature: Search Given a user default is created if does not exist When user default logs in Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds + When upload default PHOTO mediaitem with auth if does not exist and wait 5 seconds Then mediaitem is uploaded or exists Scenario: Search MediaItems diff --git a/tests/features/sharing.feature b/tests/features/sharing.feature index cdb38ece..923e2bfa 100644 --- a/tests/features/sharing.feature +++ b/tests/features/sharing.feature @@ -4,7 +4,7 @@ Feature: Sharing Given a user default is created if does not exist When user default logs in Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds + When upload default PHOTO mediaitem with auth if does not exist and wait 5 seconds Then mediaitem is uploaded or exists Scenario: Validate Create Shared Album diff --git a/tests/features/steps/albums.py b/tests/features/steps/albums.py index 35578b4e..11f45816 100644 --- a/tests/features/steps/albums.py +++ b/tests/features/steps/albums.py @@ -73,7 +73,7 @@ def step_impl(context): @then('album is present') def step_impl(context): for field in context.match_album: - assert context.album[field] == context.match_album[field] + assert context.album[field] == context.match_album[field], f"expected: {context.match_album[field]} got: {context.album[field]}" @then('album is not present in list') def step_impl(context): @@ -148,7 +148,6 @@ def step_impl(context, type): assert context.album['mediaItemsCount'] == 1 assert context.album['coverMediaItem']['id'] == context.mediaitem_id elif type =='remove': - print(context.album) assert context.album['mediaItemsCount'] == 0 assert context.album['coverMediaItem'] == None diff --git a/tests/features/steps/common.py b/tests/features/steps/common.py index a2f3fb82..e768d3a0 100644 --- a/tests/features/steps/common.py +++ b/tests/features/steps/common.py @@ -8,7 +8,7 @@ CREATED_USER = { 'default': { 'name': 'John Doe', 'username': 'johndoe', 'password': 'johndoeT3st!','features':'{"albums":true,'+ - '"favourites":true,"hidden":true,"trash":true,"explore":true,"places":true,"things":true,'+ + '"favourites":true,"hidden":true,"trash":true,"explore":true,"places":true,'+ '"people":true,"sharing":true}' }, 'jobs': { @@ -19,37 +19,35 @@ UPDATED_USER = { 'default': { 'name': 'UpdatedJohn Doe', 'username': 'updatedjohndoe', 'password': 'updatedjohndoeT3st!','features':'{"albums"'+ - ':true,"favourites":true,"hidden":true,"trash":true,"explore":true,"places":true,"things":true,'+ + ':true,"favourites":true,"hidden":true,"trash":true,"explore":true,"places":true,'+ '"people":true,"sharing":true}' }, 'jobs': { 'name': 'Steve Updated Jobs', 'features':'{"albums":true,"favourites":true,"hidden":true,"trash":true,'+ - '"explore":true,"places":true,"things":true,"people":true,"sharing":true,"jobs":true}' + '"explore":true,"places":true,"people":true,"sharing":true,"jobs":true}' } } -CREATED_ALBUM = {'name': 'Album Name', 'description': 'Album Description'} -CREATED_SHARED_ALBUM = {'name': 'Album Name', 'description': 'Album Description', 'shared': True} -UPDATED_ALBUM = {'name': 'Updated Album Name', 'description': 'Updated Album Description'} +CREATED_ALBUM = {'name': 'Album Name', 'description': 'Album Description', 'shared': False, 'hidden': False} +CREATED_SHARED_ALBUM = {'name': 'Album Name', 'description': 'Album Description', 'shared': True, 'hidden': False} +UPDATED_ALBUM = {'name': 'Updated Album Name', 'description': 'Updated Album Description', 'shared': False, 'hidden': False} -CREATED_MEDIAITEM = {'photo':{'filename': 'IMG_0543.HEIC', 'mimeType': 'image/heic', 'status': 'READY', 'cameraMake': 'Apple', - 'cameraModel': 'iPhone 12 mini', 'focalLength': '4.2', 'apertureFNumber': '1.6', 'isoEquivalent': '640', - 'exposureTime': '0.04', 'mediaItemType': 'photo', 'mediaItemCategory': 'default', +CREATED_MEDIAITEM = {'PHOTO':{'filename': 'IMG_0543.HEIC', 'mimeType': 'image/heic', 'status': 'READY', 'cameraMake': 'Apple', + 'cameraModel': 'iPhone 12 mini', 'focalLength': '4.2 mm', 'apertureFNumber': '1.6', 'isoEquivalent': '640', + 'exposureTime': '1/25', 'mediaItemType': 'PHOTO', 'mediaItemCategory': 'LIVE', 'description': None, 'favourite': False, 'hidden': False}, - 'video':{'filename': 'IMG_6470.MOV', 'mimeType': 'video/quicktime', 'status': 'READY', 'cameraMake': 'Apple', - 'cameraModel': 'iPhone 12 mini', 'fps': '30', 'mediaItemType': 'video', 'mediaItemCategory': 'default', + 'VIDEO':{'filename': 'IMG_6470.MOV', 'mimeType': 'video/quicktime', 'status': 'READY', 'cameraMake': 'Apple', + 'cameraModel': 'iPhone 12 mini', 'fps': '30', 'mediaItemType': 'VIDEO', 'mediaItemCategory': 'DEFAULT', 'description': None, 'favourite': False, 'hidden': False}} -UPDATED_MEDIAITEM = {'photo':{'filename': 'IMG_0543.HEIC', 'mimeType': 'image/heic', 'status': 'READY', 'cameraMake': 'Apple', - 'cameraModel': 'iPhone 12 mini', 'focalLength': '4.2', 'apertureFNumber': '1.6', 'isoEquivalent': '640', - 'exposureTime': '0.04', 'mediaItemType': 'photo', 'mediaItemCategory': 'default', +UPDATED_MEDIAITEM = {'PHOTO':{'filename': 'IMG_0543.HEIC', 'mimeType': 'image/heic', 'status': 'READY', 'cameraMake': 'Apple', + 'cameraModel': 'iPhone 12 mini', 'focalLength': '4.2 mm', 'apertureFNumber': '1.6', 'isoEquivalent': '640', + 'exposureTime': '1/25', 'mediaItemType': 'PHOTO', 'mediaItemCategory': 'LIVE', 'description': 'Updated MediaItem Description', 'favourite': True, 'hidden': False}, - 'video':{'filename': 'IMG_6470.MOV', 'mimeType': 'video/quicktime', 'status': 'READY', 'cameraMake': 'Apple', - 'cameraModel': 'iPhone 12 mini', 'fps': '30', 'mediaItemType': 'video', 'mediaItemCategory': 'default', + 'VIDEO':{'filename': 'IMG_6470.MOV', 'mimeType': 'video/quicktime', 'status': 'READY', 'cameraMake': 'Apple', + 'cameraModel': 'iPhone 12 mini', 'fps': '30', 'mediaItemType': 'VIDEO', 'mediaItemCategory': 'DEFAULT', 'description': None, 'favourite': False, 'hidden': False}} -CREATED_PLACE = {'name': 'Mumbai', 'city': 'Mumbai', 'state': 'Maharashtra', 'postcode': '400050', 'country': 'India'} - -CREATED_THING = {'name': 'Pizza'} +CREATED_PLACE = {'name': 'Mumbai', 'area': 'Zone 3', 'locality': 'Mumbai', 'postcode': '400050', 'country': 'India'} FILES_TO_SKIP = ['3839-samsung - sm-g973u - 16bit (2.1132075471698).dng', '1087-leica - leica m monochrom (typ 246) - 12bit (3:2).dng', '672-pentax - pentax optio s4.raw', '778-xiaomi - yi.raw', '3896-phase one - iq4 150mp - unknown (8) (4:3).iiq', diff --git a/tests/features/steps/explore.py b/tests/features/steps/explore.py index 4ec0cd3c..782a0b21 100644 --- a/tests/features/steps/explore.py +++ b/tests/features/steps/explore.py @@ -1,7 +1,7 @@ from behave import * import requests -from common import API_URL, CREATED_PLACE, CREATED_THING +from common import API_URL, CREATED_PLACE @when('get all explored {type} for mediaitem {condition} auth') @@ -14,8 +14,6 @@ def step_impl(context, type, condition): context.response = res if type == 'places': context.places = res.json() - elif type == 'things': - context.things = res.json() elif type == 'people': context.people = res.json() @@ -28,8 +26,6 @@ def step_impl(context, type, condition): context.response = res if type == 'places': context.places = res.json() - elif type == 'things': - context.things = res.json() elif type == 'people': context.people = res.json() @@ -38,13 +34,11 @@ def step_impl(context, type, condition): headers = None if condition == 'with': headers = {'Authorization': f'Bearer {context.access_token}'} - type_id = context.place_id if type == 'place' else context.thing_id if type == 'thing' else context.person_id if type == 'person' else None + type_id = context.place_id if type == 'place' else context.person_id if type == 'person' else None res = requests.get(API_URL+'/v1/explore/'+get_plural(type)+'/'+type_id, headers=headers) context.response = res if type == 'place': context.place = res.json() - elif type == 'thing': - context.thing = res.json() elif type == 'person': context.person = res.json() @@ -52,14 +46,8 @@ def step_impl(context, type, condition): def step_impl(context, type): if type == 'place': assert len(context.places) == 1 - assert context.places[0]['name'] == context.match_place['name'] - assert context.places[0]['city'] == context.match_place['city'] - assert context.places[0]['state'] == context.match_place['state'] - assert context.places[0]['country'] == context.match_place['country'] - assert context.places[0]['postcode'] == context.match_place['postcode'] - elif type == 'thing': - assert len(context.things) == 1 - assert context.things[0]['name'] == context.match_thing['name'] + for field in context.match_place: + assert context.places[0][field] == context.match_place[field] elif type == 'person': assert len(context.people) >= 1 assert context.person_id in [person['id'] for person in context.people] @@ -67,23 +55,13 @@ def step_impl(context, type): @then('explored {type} is present {condition} cover mediaitem') def step_impl(context, type, condition): if type == 'place': - assert context.place['name'] == context.match_place['name'] - assert context.place['city'] == context.match_place['city'] - assert context.place['state'] == context.match_place['state'] - assert context.place['country'] == context.match_place['country'] - assert context.place['postcode'] == context.match_place['postcode'] + for field in context.match_place: + assert context.places[0][field] == context.match_place[field] if condition == 'with': assert len(context.place['coverMediaItem'].items()) > 0 assert 'id' in context.place['coverMediaItem'].keys() else: assert 'coverMediaItem' not in context.place.items() - elif type == 'thing': - assert context.thing['name'] == context.match_thing['name'] - if condition == 'with': - assert len(context.thing['coverMediaItem'].items()) > 0 - assert 'id' in context.thing['coverMediaItem'].keys() - else: - assert 'coverMediaItem' not in context.thing.items() elif type == 'person': assert context.person['id'] == context.person_id if condition == 'with': @@ -97,13 +75,11 @@ def step_impl(context, type, condition): headers = None if condition == 'with': headers = {'Authorization': f'Bearer {context.access_token}'} - type_id = context.place_id if type == 'place' else context.thing_id if type == 'thing' else None + type_id = context.place_id if type == 'place' else None res = requests.get(API_URL+'/v1/explore/'+get_plural(type)+'/'+type_id+'/mediaItems', headers=headers) context.response = res if type == 'place': context.place_mediaitems = res.json() - elif type == 'thing': - context.thing_mediaitems = res.json() @then('mediaitem with {type} is present in list') def step_impl(context, type): @@ -112,11 +88,6 @@ def step_impl(context, type): for field in context.match_mediaitem: if context.match_mediaitem[field] != None: assert context.place_mediaitems[0][field] == context.match_mediaitem[field] - elif type == 'thing': - assert len(context.thing_mediaitems) == 1 - for field in context.match_mediaitem: - if context.match_mediaitem[field] != None: - assert context.thing_mediaitems[0][field] == context.match_mediaitem[field] @given('a mediaitem exists with {type}') def step_impl(context, type): @@ -132,9 +103,6 @@ def step_impl(context, type): if type == 'place': context.place_id = types[0]['id'] context.match_place = CREATED_PLACE - elif type == 'thing': - context.thing_id = types[0]['id'] - context.match_thing = CREATED_THING elif type == 'person': context.person_id = types[0]['id'] context.match_people = {} diff --git a/tests/features/steps/jobs.py b/tests/features/steps/jobs.py index 9d12ca4a..8919e2ad 100644 --- a/tests/features/steps/jobs.py +++ b/tests/features/steps/jobs.py @@ -72,13 +72,11 @@ def step_impl(context, component): headers = {'Authorization': f'Bearer {context.access_token}'} res = requests.get(API_URL+'/v1/mediaItems/'+context.mediaitem_id+'/'+component, headers=headers) context.response = res - context.mediaitem_things = res.json() + context.mediaitem_component[component] = res.json() @then('job mediaitem related {component} are {condition} in list') def step_impl(context, component, condition): - data = context.mediaitem_things if component == 'things' else \ - context.mediaitem_people if component == 'people' else \ - context.mediaitem_places if component == 'places' else [] + data = context.mediaitem_component[component] if component in context.mediaitem_component else [] if condition == 'absent': assert len(data) == 0 else: diff --git a/tests/features/steps/mediaitems.py b/tests/features/steps/mediaitems.py index 498338b7..67c2a5ba 100644 --- a/tests/features/steps/mediaitems.py +++ b/tests/features/steps/mediaitems.py @@ -68,7 +68,6 @@ def step_impl(context): @then('mediaitem is not present in list') def step_impl(context): - print(context.mediaitems) assert len(context.mediaitems) == 0 @then('mediaitem is not present') @@ -82,7 +81,7 @@ def step_impl(context, name, type, condition, seconds): headers = None if condition == 'with': headers = {'Authorization': f'Bearer {context.access_token}'} - files = {'file': open(f'data/{"IMG_0543.HEIC" if name == "default" and type == "photo" else "IMG_6470.MOV" if name == "default" and type =="video" else name}','rb')} + files = {'file': open(f'data/{"IMG_0543.HEIC" if name == "default" and type == "PHOTO" else "IMG_6470.MOV" if name == "default" and type =="VIDEO" else name}','rb')} res = requests.post(API_URL+'/v1/mediaItems', files=files, headers=headers) context.response = res context.mediaitem_type = type @@ -95,7 +94,7 @@ def step_impl(context, name, type, seconds): headers={'Authorization': f'Bearer {context.access_token}'}) mediaitems = res.json() if len(mediaitems) == 0: - files = {'file': open(f'data/{"IMG_0543.HEIC" if name == "default" and type == "photo" else "IMG_6470.MOV" if name == "default" and type =="video" else name}','rb')} + files = {'file': open(f'data/{"IMG_0543.HEIC" if name == "default" and type == "PHOTO" else "IMG_6470.MOV" if name == "default" and type =="VIDEO" else name}','rb')} res = requests.post(API_URL+'/v1/mediaItems', files=files, headers=headers) time.sleep(int(seconds)) context.response = res diff --git a/tests/features/steps/sharing.py b/tests/features/steps/sharing.py index e2e1e0b3..ed1801ee 100644 --- a/tests/features/steps/sharing.py +++ b/tests/features/steps/sharing.py @@ -121,7 +121,6 @@ def step_impl(context, type): assert context.album['mediaItemsCount'] == 1 assert context.album['coverMediaItem']['id'] == context.mediaitem_id elif type =='remove': - print(context.album) assert context.album['mediaItemsCount'] == 0 assert context.album['coverMediaItem'] == None diff --git a/tests/features/things.feature b/tests/features/things.feature deleted file mode 100644 index 1b1c4a7b..00000000 --- a/tests/features/things.feature +++ /dev/null @@ -1,31 +0,0 @@ -Feature: Things - - Background: Setup User and MediaItem - Given a user default is created if does not exist - When user default logs in - Then token is generated - When upload default photo mediaitem with auth if does not exist and wait 5 seconds - Then mediaitem is uploaded or exists - - Scenario: Validate Things - Given a mediaitem exists with thing - When get all explored things for mediaitem without auth - Then auth error is found - When get all explored things for mediaitem with auth - Then explored thing is present in list - When get explored thing without auth - Then auth error is found - When get explored thing with auth - Then explored thing is present with cover mediaitem - When get all explored things without auth - Then auth error is found - When get all explored things with auth - Then explored thing is present in list - When get all mediaitems for thing without auth - Then auth error is found - When get all mediaitems for thing with auth - Then mediaitem with thing is present in list - When delete mediaitem with auth - Then mediaitem is deleted - When get explored thing with auth - Then explored thing is present without cover mediaitem diff --git a/ui/.dockerignore b/ui/.dockerignore new file mode 100644 index 00000000..9b8d5147 --- /dev/null +++ b/ui/.dockerignore @@ -0,0 +1,4 @@ +.react-router +build +node_modules +README.md \ No newline at end of file diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json new file mode 100644 index 00000000..5e603ecd --- /dev/null +++ b/ui/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "react-app" +} diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 00000000..eb5c3765 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,34 @@ +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +lib-cov +coverage +*.lcov +node_modules/ +jspm_packages/ +.npm +.eslintcache +.node_repl_history +*.tgz +.yarn-integrity +.env +.env.* +!.env.example +**/.vitepress/dist +**/.vitepress/cache +.vscode-test +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.DS_Store +.react-router/ +build/ diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 00000000..207bf937 --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine AS development-dependencies-env +COPY . /app +WORKDIR /app +RUN npm ci + +FROM node:20-alpine AS production-dependencies-env +COPY ./package.json package-lock.json /app/ +WORKDIR /app +RUN npm ci --omit=dev + +FROM node:20-alpine AS build-env +COPY . /app/ +COPY --from=development-dependencies-env /app/node_modules /app/node_modules +WORKDIR /app +RUN npm run build + +FROM node:20-alpine +COPY ./package.json package-lock.json /app/ +COPY --from=production-dependencies-env /app/node_modules /app/node_modules +COPY --from=build-env /app/build /app/build +WORKDIR /app +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 00000000..b9840541 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,84 @@ +# Welcome to React Router! + +A modern, production-ready template for building full-stack React applications using React Router. + +## Features + +- 🚀 Server-side rendering +- ⚡️ Hot Module Replacement (HMR) +- 📦 Asset bundling and optimization +- 🔄 Data loading and mutations +- 🎉 TailwindCSS for styling +- 📖 [React Router docs](https://reactrouter.com/) + +## Getting Started + +### Installation + +Install the dependencies: + +```bash +npm install +``` + +### Development + +Start the development server with HMR: + +```bash +npm run dev +``` + +Your application will be available at `http://localhost:5173`. + +## Building for Production + +Create a production build: + +```bash +npm run build +``` + +## Deployment + +### Docker Deployment + +To build and run using Docker: + +```bash +docker build -t my-app . + +# Run the container +docker run -p 3000:3000 my-app +``` + +The containerized application can be deployed to any platform that supports Docker, including: + +- AWS ECS +- Google Cloud Run +- Azure Container Apps +- Digital Ocean App Platform +- Fly.io +- Railway + +### DIY Deployment + +If you're familiar with deploying Node applications, the built-in app server is production-ready. + +Make sure to deploy the output of `npm run build` + +``` +├── package.json +├── package-lock.json (or pnpm-lock.yaml, or bun.lockb) +├── build/ +│ ├── client/ # Static assets +│ └── server/ # Server-side code +``` + +## Styling + +This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. + +--- + +Built with ❤️ using React Router. diff --git a/ui/app/api.js b/ui/app/api.js new file mode 100644 index 00000000..4651687a --- /dev/null +++ b/ui/app/api.js @@ -0,0 +1,35 @@ +import axios from "axios"; + +const BASIC_AUTH_ROUTES = ["/v1/users", "/v1/jobs"]; + +export const api = axios.create({ + baseURL: "http://localhost:5001", + headers: { + "Content-Type": "application/json", + }, +}); + +api.interceptors.request.use( + (config) => { + if (BASIC_AUTH_ROUTES.find((route) => config.url.includes(route))) { + config.headers["Authorization"] = + `Basic ${localStorage.getItem("adminToken")}`; + } else { + config.headers["Authorization"] = + `Bearer ${localStorage.getItem("accessToken")}`; + } + return config; + }, + (error) => Promise.reject(error) +); + +export const getErrorMessage = (status) => { + switch (status) { + case 401: + return "Incorrect username or password"; + case 400: + return "Bad Request"; + default: + return "Some error. Try again!"; + } +}; diff --git a/ui/app/api/admin.js b/ui/app/api/admin.js new file mode 100644 index 00000000..4cdfaebf --- /dev/null +++ b/ui/app/api/admin.js @@ -0,0 +1,50 @@ +import { api } from "../api"; + +const login = async (username, password) => { + const basicAuth = btoa(`${username}:${password}`); + localStorage.setItem("adminToken", basicAuth); + const response = await api.get("/v1/users", { + headers: { + Authorization: `Basic ${basicAuth}`, + }, + }); + + if (response.status !== 200) { + localStorage.removeItem("adminToken"); + throw new Error("failed to login"); + } +}; + +const getUsers = async () => { + const response = await api.get("/v1/users"); + if (response.status === 200) { + return response.data; + } + throw new Error("failed to get users"); +}; + +const createUser = async (user) => { + const response = await api.post("/v1/users", user); + if (response.status === 201) { + return response.data; + } + throw new Error("failed to create user"); +}; + +const updateUser = async (id, user) => { + const response = await api.put(`/v1/users/${id}`, user); + if (response.status === 204) { + return response.data; + } + throw new Error("failed to update user"); +}; + +const deleteUser = async (id) => { + const response = await api.delete(`/v1/users/${id}`); + if (response.status === 204) { + return response.data; + } + throw new Error("failed to delete user"); +}; + +export { login, getUsers, createUser, updateUser, deleteUser }; diff --git a/ui/app/app.css b/ui/app/app.css new file mode 100644 index 00000000..cc566d2f --- /dev/null +++ b/ui/app/app.css @@ -0,0 +1,62 @@ +@import "tailwindcss"; + +html, +body { + font-family: "DM Sans", sans-serif; +} + +.smriti { + background-color: #0B81E0; +} + +.smriti:hover { + background-color: #288CE5; +} + +a { + color: #0B81E0; +} + +a:hover { + color: #288CE5; +} + +.smriti-dark, h1, h2, h3, h4, h5, h6 { + color: #3d464d; +} + +.smriti-error-bg { + background-color: #FEF2F2; +} + +.smriti-error { + color: #D26060 +} + +.smriti-error-border { + border-color: #E2A0A0 +} + +.smriti-warn-bg { + background-color: #FFFAE4; +} + +.smriti-warn { + color: #C39D02 +} + +.smriti-warn-border { + border-color: #F4CD33 +} + +.smriti-success-bg { + background-color: #E8F7ED; +} + +.smriti-success { + color: #1BA84E +} + +.smriti-success-border { + border-color: #30B661 +} \ No newline at end of file diff --git a/ui/app/pages/admin/index.jsx b/ui/app/pages/admin/index.jsx new file mode 100644 index 00000000..73ecfdf2 --- /dev/null +++ b/ui/app/pages/admin/index.jsx @@ -0,0 +1,90 @@ +import { useState } from "react"; +import { login } from "../../api/admin"; +import { useNavigate } from "react-router"; +import { getErrorMessage } from "../../api"; + +export function meta() { + return [{ title: "Admin Login - Smriti" }]; +} + +export default function Login() { + const navigate = useNavigate(); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const handleLogin = async (e) => { + e.preventDefault(); + setLoading(true); + setError(""); + + try { + await login(username, password); + navigate("/admin/users"); + } catch (err) { + setError(getErrorMessage(err?.response?.status)); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+
+ Admin Login +
+
+ or{" "} + + setup an admin + +
+
+
+ { + setUsername(e.target.value); + setError(""); + }} + autoComplete="off" + required + /> + { + setPassword(e.target.value); + setError(""); + }} + autoComplete="off" + required + /> + {error && ( +
+ {error} +
+ )} + +
+ Smriti +
+
+ ); +} diff --git a/ui/app/pages/admin/jobs.jsx b/ui/app/pages/admin/jobs.jsx new file mode 100644 index 00000000..b83593b1 --- /dev/null +++ b/ui/app/pages/admin/jobs.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Admin Jobs - Smriti" }]; +} + +export default function Users() { + return

Admin Jobs

; +} diff --git a/ui/app/pages/admin/users.jsx b/ui/app/pages/admin/users.jsx new file mode 100644 index 00000000..c6fc7c61 --- /dev/null +++ b/ui/app/pages/admin/users.jsx @@ -0,0 +1,167 @@ +import { useEffect, useState } from "react"; +import { createUser, getUsers, updateUser } from "../../api/admin"; + +export function meta() { + return [{ title: "Admin Users - Smriti" }]; +} + +function CreateUser() { + const [name, setName] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + + const handleCreateUser = async (e) => { + e.preventDefault(); + setLoading(true); + setError(""); + setSuccess(""); + + try { + let res = await createUser({ name, username, password }); + setSuccess(res); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + return ( +
+ {success && ( +
{JSON.stringify(success)}
+ )} + {error &&
{JSON.stringify(error)}
} + setName(e.target.value)} + required + /> + setUsername(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + +
+ ); +} + +function UpdateUser(user) { + const [id, _] = useState(user.id); + const [name, setName] = useState(user.name); + const [username, setUsername] = useState(user.username); + const [password, setPassword] = useState(user.password); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + + const handleUpdateUser = async (e) => { + e.preventDefault(); + setLoading(true); + setError(""); + setSuccess(""); + + try { + let res = await updateUser(id, { name, username, password }); + setSuccess(res); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }; + + return ( +
+ {success && ( +
{JSON.stringify(success)}
+ )} + {error &&
{JSON.stringify(error)}
} + setName(e.target.value)} + required + /> + setUsername(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + +
+ ); +} + +function ListUsers() { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + try { + setLoading(true); + let res = getUsers(); + res.then((data) => { + setUsers(data); + }); + } catch (err) { + setError(err); + } finally { + setLoading(false); + } + }, []); + + return ( +
+

Users

+ {(!loading && + users && + users.length > 0 && + users.map((user) => )) || ( +
+ {error &&
{JSON.stringify(error)}
} +
+ )} +
+ ); +} + +export default function Users() { + return ( +
+

Admin Users

+ + +
+ ); +} diff --git a/ui/app/pages/albums/album.jsx b/ui/app/pages/albums/album.jsx new file mode 100644 index 00000000..7b30b4cb --- /dev/null +++ b/ui/app/pages/albums/album.jsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router"; + +export function meta() { + return [{ title: "(TODO) Album Name - Smriti" }]; +} + +export default function Album() { + const { id } = useParams(); + + return

Album: {id}

; +} diff --git a/ui/app/pages/albums/index.jsx b/ui/app/pages/albums/index.jsx new file mode 100644 index 00000000..ce0e0335 --- /dev/null +++ b/ui/app/pages/albums/index.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Albums - Smriti" }]; +} + +export default function Albums() { + return

Albums

; +} diff --git a/ui/app/pages/auth/login.jsx b/ui/app/pages/auth/login.jsx new file mode 100644 index 00000000..d359a7f4 --- /dev/null +++ b/ui/app/pages/auth/login.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Login - Smriti" }]; +} + +export default function Login() { + return

Users Login

; +} diff --git a/ui/app/pages/explore/people.jsx b/ui/app/pages/explore/people.jsx new file mode 100644 index 00000000..d8a9c133 --- /dev/null +++ b/ui/app/pages/explore/people.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "People - Smriti" }]; +} + +export default function People() { + return

People

; +} diff --git a/ui/app/pages/explore/person.jsx b/ui/app/pages/explore/person.jsx new file mode 100644 index 00000000..757f8818 --- /dev/null +++ b/ui/app/pages/explore/person.jsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router"; + +export function meta() { + return [{ title: "(TODO) Person Name - Smriti" }]; +} + +export default function Person() { + const { id } = useParams(); + + return

Person: {id}

; +} diff --git a/ui/app/pages/explore/place.jsx b/ui/app/pages/explore/place.jsx new file mode 100644 index 00000000..f9b4473f --- /dev/null +++ b/ui/app/pages/explore/place.jsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router"; + +export function meta() { + return [{ title: "(TODO) Place Name - Smriti" }]; +} + +export default function Place() { + const { id } = useParams(); + + return

Place: {id}

; +} diff --git a/ui/app/pages/explore/places.jsx b/ui/app/pages/explore/places.jsx new file mode 100644 index 00000000..c0afb097 --- /dev/null +++ b/ui/app/pages/explore/places.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Places - Smriti" }]; +} + +export default function Places() { + return

Places

; +} diff --git a/ui/app/pages/explore/thing.jsx b/ui/app/pages/explore/thing.jsx new file mode 100644 index 00000000..6ca4e504 --- /dev/null +++ b/ui/app/pages/explore/thing.jsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router"; + +export function meta() { + return [{ title: "(TODO) Thing Name - Smriti" }]; +} + +export default function Thing() { + const { id } = useParams(); + + return

Thing: {id}

; +} diff --git a/ui/app/pages/explore/things.jsx b/ui/app/pages/explore/things.jsx new file mode 100644 index 00000000..c84fe2c5 --- /dev/null +++ b/ui/app/pages/explore/things.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Things - Smriti" }]; +} + +export default function Things() { + return

Things

; +} diff --git a/ui/app/pages/home/index.jsx b/ui/app/pages/home/index.jsx new file mode 100644 index 00000000..2e28aa9c --- /dev/null +++ b/ui/app/pages/home/index.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Home - Smriti" }]; +} + +export default function Home() { + return

Home

; +} diff --git a/ui/app/pages/library/favourites.jsx b/ui/app/pages/library/favourites.jsx new file mode 100644 index 00000000..2f0e800f --- /dev/null +++ b/ui/app/pages/library/favourites.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Favourites - Smriti" }]; +} + +export default function Favourites() { + return

Favourites

; +} diff --git a/ui/app/pages/library/hidden.jsx b/ui/app/pages/library/hidden.jsx new file mode 100644 index 00000000..1c6768a1 --- /dev/null +++ b/ui/app/pages/library/hidden.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Hidden - Smriti" }]; +} + +export default function Hidden() { + return

Hidden

; +} diff --git a/ui/app/pages/library/trash.jsx b/ui/app/pages/library/trash.jsx new file mode 100644 index 00000000..a9180848 --- /dev/null +++ b/ui/app/pages/library/trash.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Trash - Smriti" }]; +} + +export default function Trash() { + return

Trash

; +} diff --git a/ui/app/pages/sharing/index.jsx b/ui/app/pages/sharing/index.jsx new file mode 100644 index 00000000..86733668 --- /dev/null +++ b/ui/app/pages/sharing/index.jsx @@ -0,0 +1,7 @@ +export function meta() { + return [{ title: "Sharing - Smriti" }]; +} + +export default function Sharing() { + return

Sharing

; +} diff --git a/ui/app/pages/sharing/sharedalbum.jsx b/ui/app/pages/sharing/sharedalbum.jsx new file mode 100644 index 00000000..b95094c1 --- /dev/null +++ b/ui/app/pages/sharing/sharedalbum.jsx @@ -0,0 +1,11 @@ +import { useParams } from "react-router"; + +export function meta() { + return [{ title: "(TODO) Shared Album Name - Smriti" }]; +} + +export default function SharedAlbum() { + const { id } = useParams(); + + return

Shared Album: {id}

; +} diff --git a/ui/app/root.jsx b/ui/app/root.jsx new file mode 100644 index 00000000..9ec8f7a2 --- /dev/null +++ b/ui/app/root.jsx @@ -0,0 +1,77 @@ +import { + isRouteErrorResponse, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "react-router"; + +import "./app.css"; + +export const links = () => [ + { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous", + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap", + }, +]; + +export function Layout({ children }) { + return ( + + + + + + + + + {children} + + + + + ); +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }) { + let message = "Oops!"; + let details = "An unexpected error occurred."; + let stack; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? "404" : "Error"; + details = + error.status === 404 + ? "The requested page could not be found." + : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+          {stack}
+        
+ )} +
+ ); +} diff --git a/ui/app/routes.js b/ui/app/routes.js new file mode 100644 index 00000000..85df5571 --- /dev/null +++ b/ui/app/routes.js @@ -0,0 +1,23 @@ +import { index, route, prefix } from "@react-router/dev/routes"; + +export default [ + ...prefix("admin", [ + index("pages/admin/index.jsx"), + route("users", "pages/admin/users.jsx"), + ]), + index("pages/home/index.jsx"), + ...prefix("auth", [route("login", "pages/auth/login.jsx")]), + route("albums", "pages/albums/index.jsx"), + route("album/:id", "pages/albums/album.jsx"), + route("sharing", "pages/sharing/index.jsx"), + route("share/:id", "pages/sharing/sharedalbum.jsx"), + route("favourites", "pages/library/favourites.jsx"), + route("hidden", "pages/library/hidden.jsx"), + route("trash", "pages/library/trash.jsx"), + route("things", "pages/explore/things.jsx"), + route("thing/:id", "pages/explore/thing.jsx"), + route("places", "pages/explore/places.jsx"), + route("place/:id", "pages/explore/place.jsx"), + route("people", "pages/explore/people.jsx"), + route("person/:id", "pages/explore/person.jsx"), +]; diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 00000000..f983aa6c --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,10626 @@ +{ + "name": "ui", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "dependencies": { + "@react-router/node": "^7.7.1", + "@react-router/serve": "^7.7.1", + "@tailwindcss/vite": "^4.1.12", + "axios": "^1.11.0", + "isbot": "^5.1.27", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.7.1", + "tailwindcss": "^4.1.12" + }, + "devDependencies": { + "@react-router/dev": "^7.7.1", + "eslint": "^8.57.1", + "eslint-config-react-app": "^7.0.1", + "vite": "^6.3.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz", + "integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mjackson/node-fetch-server": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz", + "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", + "license": "MIT" + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.1.0", + "glob": "^10.2.2", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-router/dev": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.8.0.tgz", + "integrity": "sha512-5NA9yLZComM+kCD3zNPL3rjrAFjzzODY8hjAJlpz/6jpyXoF28W8QTSo8rxc56XVNLONM75Y5nq1wzeEcWFFKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.7", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "@npmcli/package-json": "^4.0.1", + "@react-router/node": "7.8.0", + "@vitejs/plugin-react": "^4.5.2", + "@vitejs/plugin-rsc": "0.4.11", + "arg": "^5.0.1", + "babel-dead-code-elimination": "^1.0.6", + "chokidar": "^4.0.0", + "dedent": "^1.5.3", + "es-module-lexer": "^1.3.1", + "exit-hook": "2.2.1", + "isbot": "^5.1.11", + "jsesc": "3.0.2", + "lodash": "^4.17.21", + "pathe": "^1.1.2", + "picocolors": "^1.1.1", + "prettier": "^3.6.2", + "react-refresh": "^0.14.0", + "semver": "^7.3.7", + "set-cookie-parser": "^2.6.0", + "tinyglobby": "^0.2.14", + "valibot": "^0.41.0", + "vite-node": "^3.2.2" + }, + "bin": { + "react-router": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@react-router/serve": "^7.8.0", + "react-router": "^7.8.0", + "typescript": "^5.1.0", + "vite": "^5.1.0 || ^6.0.0 || ^7.0.0", + "wrangler": "^3.28.2 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@react-router/serve": { + "optional": true + }, + "typescript": { + "optional": true + }, + "wrangler": { + "optional": true + } + } + }, + "node_modules/@react-router/express": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.8.0.tgz", + "integrity": "sha512-lNUwux5IfMqczIL3gXZ/mauPUoVz65fSLPnUTkP7hkh/P7fcsPtYkmcixuaWb+882lY+Glf157OdoIMbcSMBaA==", + "license": "MIT", + "dependencies": { + "@react-router/node": "7.8.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "express": "^4.17.1 || ^5", + "react-router": "7.8.0", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/node": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.8.0.tgz", + "integrity": "sha512-/FFN9vqI2EHPwDCHTvsMInhrYvwJ5SlCeyUr1oWUxH47JyYkooVFks5++M4VkrTgj2ZBsMjPPKy0xRNTQdtBDA==", + "license": "MIT", + "dependencies": { + "@mjackson/node-fetch-server": "^0.2.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.8.0", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/serve": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.8.0.tgz", + "integrity": "sha512-DokCv1GfOMt9KHu+k3WYY9sP5nOEzq7za+Vi3dWPHoY5oP0wgv8S4DnTPU08ASY8iFaF38NAzapbSFfu6Xfr0Q==", + "license": "MIT", + "dependencies": { + "@react-router/express": "7.8.0", + "@react-router/node": "7.8.0", + "compression": "^1.7.4", + "express": "^4.19.2", + "get-port": "5.1.1", + "morgan": "^1.10.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "react-router-serve": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.8.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", + "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", + "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", + "tailwindcss": "4.1.12" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vitejs/plugin-rsc": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-rsc/-/plugin-rsc-0.4.11.tgz", + "integrity": "sha512-+4H4wLi+Y9yF58znBfKgGfX8zcqUGt8ngnmNgzrdGdF1SVz7EO0sg7WnhK5fFVHt6fUxsVEjmEabsCWHKPL1Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mjackson/node-fetch-server": "^0.7.0", + "es-module-lexer": "^1.7.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17", + "periscopic": "^4.0.2", + "turbo-stream": "^3.1.0", + "vitefu": "^1.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*", + "vite": "*" + } + }, + "node_modules/@vitejs/plugin-rsc/node_modules/@mjackson/node-fetch-server": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.7.0.tgz", + "integrity": "sha512-un8diyEBKU3BTVj3GzlTPA1kIjCkGdD+AMYQy31Gf9JCkfoZzwgJ79GUtHrF2BN3XPNMLpubbzPcxys+a3uZEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", + "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-preset-react-app": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.1.0.tgz", + "integrity": "sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001734", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", + "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz", + "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.200", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", + "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "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.9", + "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" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "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.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbot": { + "version": "5.1.29", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.29.tgz", + "integrity": "sha512-DelDWWoa3mBoyWTq3wjp+GIWx/yZdN7zLUE7NFhKjAiJ+uJVRkbLlwykdduCE4sPUUy8mlTYTmdhBUYu91F+sw==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", + "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/periscopic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-4.0.2.tgz", + "integrity": "sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "is-reference": "^3.0.2", + "zimmerframe": "^1.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz", + "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "devOptional": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/turbo-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-3.1.0.tgz", + "integrity": "sha512-tVI25WEXl4fckNEmrq70xU1XumxUwEx/FZD5AgEcV8ri7Wvrg2o7GEq8U7htrNx3CajciGm+kDyhRf5JB6t7/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/valibot": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.41.0.tgz", + "integrity": "sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "license": "MIT", + "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" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..94794197 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,27 @@ +{ + "name": "ui", + "private": true, + "type": "module", + "scripts": { + "build": "react-router build", + "dev": "react-router dev --host 0.0.0.0", + "start": "react-router-serve ./build/server/index.js" + }, + "dependencies": { + "@react-router/node": "^7.7.1", + "@react-router/serve": "^7.7.1", + "@tailwindcss/vite": "^4.1.12", + "axios": "^1.11.0", + "isbot": "^5.1.27", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.7.1", + "tailwindcss": "^4.1.12" + }, + "devDependencies": { + "@react-router/dev": "^7.7.1", + "eslint": "^8.57.1", + "eslint-config-react-app": "^7.0.1", + "vite": "^6.3.3" + } +} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 00000000..a1c77141 Binary files /dev/null and b/ui/public/favicon.ico differ diff --git a/ui/public/img/one.png b/ui/public/img/one.png new file mode 100644 index 00000000..fc760d3b Binary files /dev/null and b/ui/public/img/one.png differ diff --git a/ui/public/logo.png b/ui/public/logo.png new file mode 100644 index 00000000..e2d37ed9 Binary files /dev/null and b/ui/public/logo.png differ diff --git a/ui/react-router.config.js b/ui/react-router.config.js new file mode 100644 index 00000000..1335664f --- /dev/null +++ b/ui/react-router.config.js @@ -0,0 +1,5 @@ +export default { + // Config options... + // Server-side render by default, to enable SPA mode set this to `false` + ssr: true, +}; diff --git a/ui/vite.config.js b/ui/vite.config.js new file mode 100644 index 00000000..2e4df669 --- /dev/null +++ b/ui/vite.config.js @@ -0,0 +1,7 @@ +import { reactRouter } from "@react-router/dev/vite"; +import { defineConfig } from "vite"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [reactRouter(), tailwindcss()], +}); diff --git a/worker/.clang-format b/worker/.clang-format new file mode 100644 index 00000000..9f82ec19 --- /dev/null +++ b/worker/.clang-format @@ -0,0 +1,12 @@ +--- +BasedOnStyle: Google +IndentWidth: 2 +ColumnLimit: 80 +SpaceAfterCStyleCast: false +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +AlignTrailingComments: true +SpacesBeforeTrailingComments: 1 +AlignConsecutiveMacros: None +UseCRLF: false +InsertNewlineAtEOF: true \ No newline at end of file diff --git a/worker/.gitignore b/worker/.gitignore index 1f903673..2565c8d9 100644 --- a/worker/.gitignore +++ b/worker/.gitignore @@ -1,50 +1,27 @@ -__pycache__/ -*.py[cod] -*$py.class +*.d +*.slo +*.lo +*.o +*.obj +*.gch +*.pch *.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST -*.manifest -*.spec -pip-log.txt -pip-delete-this-directory.txt -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +*.dylib +*.dll +*.mod +*.smod +*.lai +*.la +*.a +*.lib +*.exe +*.out +*.app Thumbs.db .DS_Store .idea/ -fly.toml \ No newline at end of file +fly.toml +build/ +generated/ +third_party/ +cppcheck* \ No newline at end of file diff --git a/worker/CMakeLists.txt b/worker/CMakeLists.txt new file mode 100644 index 00000000..879ec98d --- /dev/null +++ b/worker/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.30) + +set(CMAKE_CXX_STANDARD 17) + +project(worker) + +option(BUILD_TESTS "Build Tests and Benchmarks" OFF) + +if(NOT DEFINED VERSION) + set(VERSION "dev") +endif() + +execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_SHA + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(NOT GIT_SHA) + set(GIT_SHA "-") +endif() + +include(cmake/Dependencies.cmake) + +if(APPLE) + set(HOMEBREW_PREFIX "/opt/homebrew") + include_directories(${HOMEBREW_PREFIX}/include) + link_directories(${HOMEBREW_PREFIX}/lib) + set(LIBOMP_ROOT ${HOMEBREW_PREFIX}/opt/libomp) +endif() + +find_package(gRPC REQUIRED) +find_package(Protobuf REQUIRED) +find_package(ImageMagick COMPONENTS Magick++ REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(LibRaw REQUIRED libraw) + +file(GLOB EXEC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) + +file(GLOB GENERATED_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/generated/protos/*.cc) + +add_executable(worker ${EXEC_SOURCES} ${GENERATED_SOURCES}) + +if(APPLE) + target_include_directories(worker PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/include) + target_link_directories(worker PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/lib) + target_link_libraries(worker PRIVATE omp) +endif() + +target_include_directories(worker PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/generated + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/simdjson/include + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/simdjson/src + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/cpr/include + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${IMAGEMAGICK_INCLUDE_DIRS} + ${LIBRAW_INCLUDE_DIRS}) + +target_compile_definitions(worker PRIVATE + DEFAULT_VERSION="${VERSION}" + DEFAULT_GIT_SHA="${GIT_SHA}") + +target_link_libraries(worker PRIVATE + spdlog::spdlog + cpr::cpr + simdjson::simdjson + gRPC::grpc++ + gRPC::grpc + protobuf::libprotobuf + ImageMagick::Magick++ + ${LIBRAW_LIBRARIES}) + +set_target_properties(worker PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS OFF) + +if(BUILD_TESTS) + enable_testing() + + set(BENCHMARK_ENABLE_TESTING OFF) + set(GTEST_CREATE_TESTS OFF) + set(INSTALL_GTEST OFF) + + include(FetchContent) + FetchContent_MakeAvailable(googletest benchmark) + + enable_testing() + + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/worker/CPPLINT.cfg b/worker/CPPLINT.cfg new file mode 100644 index 00000000..a31a3387 --- /dev/null +++ b/worker/CPPLINT.cfg @@ -0,0 +1,3 @@ +set noparent +filter=-build/include_subdir,-build/c++11,-whitespace/comments,-runtime/references,-whitespace/indent_namespace +linelength=80 diff --git a/worker/Dockerfile b/worker/Dockerfile index 63d140a8..90a952dd 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -1,14 +1,27 @@ -FROM python:3.12-slim AS builder -RUN apt update && \ - apt install --no-install-recommends -y build-essential \ - curl gcc libssl-dev python3-opencv ffmpeg libmagickwand-dev \ - libimage-exiftool-perl exiftool libraw-dev -RUN curl --proto '=https' --tlsv1.3 -sSf https://sh.rustup.rs -o rustup-init.sh -RUN sh rustup-init.sh -y -ENV PATH="/root/.cargo/bin:${PATH}" -ADD requirements.txt /requirements.txt -RUN pip3 install --no-cache-dir -r requirements.txt -vvv -COPY src /app/src -EXPOSE 15002 +FROM ubuntu:24.04 AS builder +RUN apt-get update && apt-get install -y \ + build-essential cmake git wget autoconf automake libtool pkg-config \ + curl libraw-dev libmagick++-dev \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /opt +RUN git clone --recurse-submodules -b v1.71.0 https://github.com/grpc/grpc.git && \ + cd grpc && \ + mkdir -p cmake/build && cd cmake/build && \ + cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + ../.. && \ + make -j$(nproc) && \ + make install +WORKDIR /app +COPY . /app +RUN mkdir -p build && cd build && \ + cmake -DCMAKE_PREFIX_PATH=/usr/local \ + -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF .. && \ + make -j$(nproc) + +FROM gcr.io/distroless/cc WORKDIR /app -CMD ["python3", "-m", "src.main"] \ No newline at end of file +COPY --from=builder /app/build/worker . +EXPOSE 5002 +EXPOSE 15002 +CMD ["/app/worker"] \ No newline at end of file diff --git a/worker/Makefile b/worker/Makefile index 992f665a..25b879d7 100644 --- a/worker/Makefile +++ b/worker/Makefile @@ -1,28 +1,38 @@ -install: - @pip install -r requirements.txt +.PHONY: build +build: + @mkdir -p build + @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF + @cd build && make -j 4 -run: - @python3 -m src.main +.PHONY: build-with-tests +build-with-tests: + @mkdir -p build + @cmake -S . -B build -DCMAKE_PREFIX_PATH=${MY_INSTALL_DIR} -DBUILD_TESTS=ON + @cd build && make -j 4 -test-install: - @pip install pytest pytest-cov requests-mock pytest-asyncio +.PHONY: run +run: + @./build/worker +.PHONY: test test: - @python3 -m pytest + @cd build/tests && \ + ./worker_tests && \ + ./worker_benchmarks +.PHONY: cover cover: - @coverage run --source src -m pytest - @coverage report --omit=src/protos/*,*__.py - @coverage xml --omit=src/protos/*,*__.py + @echo "TODO(omkar)" +.PHONY: cover-html cover-html: cover - @coverage html --omit=src/protos/*,*__.py + @echo "TODO(omkar)" +.PHONY: lint lint: - @pylint * + @cpplint --recursive include/ src/ tests/ +.PHONY: proto proto: - @mkdir -p ../src/protos - @cp -r ../protos/* ../src/protos/ - @python3 -m grpc_tools.protoc -I../ --python_out=./ --pyi_out=./ --grpc_python_out=./ ../src/protos/*.proto - @rm -rf ../src \ No newline at end of file + @protoc -I../protos --cpp_out=./generated/protos ../protos/*.proto + @protoc -I../protos --grpc_out=generate_mock_code=true:./generated/protos --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` ../protos/*.proto diff --git a/worker/cmake/Dependencies.cmake b/worker/cmake/Dependencies.cmake new file mode 100644 index 00000000..5c8965fd --- /dev/null +++ b/worker/cmake/Dependencies.cmake @@ -0,0 +1,19 @@ +include(FetchContent) +set(FETCHCONTENT_BASE_DIR ${CMAKE_SOURCE_DIR}/third_party) +function(fetch_dependency name repo tag) + FetchContent_Declare( + ${name} + GIT_REPOSITORY ${repo} + GIT_TAG ${tag} + ) + FetchContent_MakeAvailable(${name}) +endfunction() + +fetch_dependency(cpr https://github.com/libcpr/cpr.git 1.11.2) +fetch_dependency(simdjson https://github.com/simdjson/simdjson.git v3.12.3) +fetch_dependency(spdlog https://github.com/gabime/spdlog.git v1.15.2) + +if(BUILD_TESTS) + fetch_dependency(googletest https://github.com/google/googletest.git v1.16.0) + fetch_dependency(benchmark https://github.com/google/benchmark.git v1.9.2) +endif() \ No newline at end of file diff --git a/worker/include/worker/api_client.h b/worker/include/worker/api_client.h new file mode 100644 index 00000000..1c2ce6a7 --- /dev/null +++ b/worker/include/worker/api_client.h @@ -0,0 +1,36 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include + +#include +#include + +#include "protos/api.grpc.pb.h" +#include "protos/api.pb.h" + +namespace services { + +namespace api { + +class APIClient { + public: + explicit APIClient(std::shared_ptr channel); + explicit APIClient(std::unique_ptr stub); + std::string GetWorkerConfig(); + MediaItemProcessResponse GetMediaItemProcess(); + bool SaveMediaItemMetadata(const MediaItemMetadataRequest& request); + bool SaveMediaItemPreviewThumbnail( + const MediaItemPreviewThumbnailRequest& request); + bool SaveMediaItemPlace(const MediaItemPlaceRequest& request); + bool SaveMediaItemFaces(const MediaItemFacesRequest& request); + bool SaveMediaItemPeople(const MediaItemPeopleRequest& request); + bool SaveMediaItemFinalResult(const MediaItemFinalResultRequest& request); + + private: + std::unique_ptr stub_; +}; + +} // namespace api + +} // namespace services diff --git a/worker/include/worker/components.h b/worker/include/worker/components.h new file mode 100644 index 00000000..fdcc0fda --- /dev/null +++ b/worker/include/worker/components.h @@ -0,0 +1,29 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include +#include + +namespace components { + +class Component { + public: + Component(const std::string& name, const std::string& source, + const std::string& params) + : name(name), source(source), params(params) {} + std::string name; + std::string source; + std::string params; +}; + +class ComponentConfig { + public: + ComponentConfig(const std::string& source, const std::string& params); + std::string source; + std::string params; +}; + +std::unordered_map ParseComponentConfig( + const std::string& config); + +} // namespace components diff --git a/worker/include/worker/config.h b/worker/include/worker/config.h new file mode 100644 index 00000000..d9b84ec0 --- /dev/null +++ b/worker/include/worker/config.h @@ -0,0 +1,49 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include + +#include +#include +#include +#include + +class Config { + public: + Config() { + const char* env_log_level = std::getenv("SMRITI_WORKER_LOG_LEVEL"); + log_level = spdlog::level::info; + if (env_log_level) { + std::string level_str(env_log_level); + if (level_str == "debug") + log_level = spdlog::level::debug; + else if (level_str == "info") + log_level = spdlog::level::info; + else if (level_str == "warn") + log_level = spdlog::level::warn; + else if (level_str == "error") + log_level = spdlog::level::err; + else if (level_str == "critical") + log_level = spdlog::level::critical; + } + + const char* env_port = std::getenv("SMRITI_WORKER_PORT"); + port = (env_port && std::strlen(env_port) > 0) ? std::string(env_port) + : "15002"; + + const char* env_api_host = std::getenv("SMRITI_API_HOST"); + api_host = (env_api_host && std::strlen(env_api_host) > 0) + ? std::string(env_api_host) + : "127.0.0.1"; + + const char* env_api_port = std::getenv("SMRITI_API_PORT"); + api_port = (env_api_port && std::strlen(env_api_port) > 0) + ? std::string(env_api_port) + : "15001"; + } + + spdlog::level::level_enum log_level; + std::string port; + std::string api_host; + std::string api_port; +}; diff --git a/worker/include/worker/metadata.h b/worker/include/worker/metadata.h new file mode 100644 index 00000000..3f643239 --- /dev/null +++ b/worker/include/worker/metadata.h @@ -0,0 +1,133 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace metadata { + +class ExifToolClientInterface { + public: + virtual ~ExifToolClientInterface() = default; + virtual std::unordered_map Extract( + const std::string& file_path) = 0; +}; + +class ExifToolClient : public ExifToolClientInterface { + public: + ExifToolClient() { + if (pipe(in_fd) == -1 || pipe(out_fd) == -1) { + throw std::runtime_error("pipe failed"); + } + + pid = fork(); + if (pid == -1) { + throw std::runtime_error("fork failed"); + } + + if (pid == 0) { + dup2(in_fd[0], STDIN_FILENO); + dup2(out_fd[1], STDOUT_FILENO); + close(in_fd[1]); + close(out_fd[0]); + execlp(exiftool_path.c_str(), exiftool_path.c_str(), "-stay_open", "True", + "-@", "-", static_cast(nullptr)); + _exit(1); + } + + close(in_fd[0]); + close(out_fd[1]); + + in_stream = fdopen(in_fd[1], "w"); + out_stream = fdopen(out_fd[0], "r"); + if (!in_stream || !out_stream) { + throw std::runtime_error("fdopen failed"); + } + } + + ~ExifToolClient() override { + if (in_stream) { + fprintf(in_stream, "-stay_open\nFalse\n"); + fflush(in_stream); + fclose(in_stream); + } + if (out_stream) { + fclose(out_stream); + } + waitpid(pid, nullptr, 0); + } + + std::unordered_map Extract( + const std::string& file_path) override { + std::lock_guard lock(io_mutex); + + fprintf(in_stream, "-s\n-s\n%s\n-execute\n", file_path.c_str()); + fflush(in_stream); + + std::unordered_map result; + char buffer[4096]; + while (fgets(buffer, sizeof(buffer), out_stream)) { + std::string line(buffer); + if (line.find("{ready}") != std::string::npos) { + break; + } + auto pos = line.find_first_of(':'); + if (pos != std::string::npos) { + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 2, line.length() - pos - 3); + result[key] = value; + } + } + + return result; + } + + private: + int in_fd[2], out_fd[2]; + FILE* in_stream{nullptr}; + FILE* out_stream{nullptr}; + pid_t pid; + std::mutex io_mutex; + std::string exiftool_path = "exiftool"; +}; + +class Metadata { + public: + explicit Metadata(std::shared_ptr exif_tool_client, + std::shared_ptr api_client) + : exif_tool_client_(exif_tool_client), api_client_(api_client) {} + std::unordered_map Extract( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& file_path); + + private: + std::shared_ptr exif_tool_client_; + std::shared_ptr api_client_; +}; + +std::string GetValue(const std::unordered_map& data, + const std::vector& keys); + +std::string GetCoordinates(const std::string& location); + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client); +} // namespace metadata + +} // namespace components diff --git a/worker/include/worker/places.h b/worker/include/worker/places.h new file mode 100644 index 00000000..5134fbb4 --- /dev/null +++ b/worker/include/worker/places.h @@ -0,0 +1,76 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace places { + +class HttpClientInterface { + public: + virtual ~HttpClientInterface() = default; + virtual cpr::Response Get(const cpr::Url& url, + const cpr::Header& headers) = 0; +}; + +class HttpClient : public HttpClientInterface { + public: + cpr::Response Get(const cpr::Url& url, const cpr::Header& headers) override { + return cpr::Get(url, headers); + } +}; + +class Places { + public: + Places() {} + virtual std::unordered_map ReverseGeocode( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& latitude, + const std::string& longitude); +}; + +class OpenStreetMap : public Places { + public: + explicit OpenStreetMap( + std::shared_ptr http_client, + std::shared_ptr api_client, + std::string url = + "https://nominatim.openstreetmap.org/" + "reverse.php?zoom=18&format=jsonv2&lat={lat}&lon={lon}", + int timeout = 60) + : http_client_(http_client), + api_client_(api_client), + url_(std::move(url)), + timeout_(timeout) {} + std::unordered_map ReverseGeocode( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& latitude, + const std::string& longitude) override; + + private: + std::shared_ptr http_client_; + std::shared_ptr api_client_; + std::string url_; + int timeout_; +}; + +std::string Format(std::string input, + const std::unordered_map& values); + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client); +} // namespace places + +} // namespace components diff --git a/worker/include/worker/preview_thumbnail.h b/worker/include/worker/preview_thumbnail.h new file mode 100644 index 00000000..d5219418 --- /dev/null +++ b/worker/include/worker/preview_thumbnail.h @@ -0,0 +1,165 @@ +// Copyright 2025 Omkar Prabhu +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace previewthumbnail { + +inline std::string Base64Encode(const std::vector& data) { + static const char table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + std::string result; + unsigned int val = 0; + int valb = -6; + + for (unsigned char c : data) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + result.push_back(table[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { + result.push_back(table[((val << 8) >> (valb + 8)) & 0x3F]); + } + + while (result.size() % 4) { + result.push_back('='); + } + + return result; +} + +class ImageConverterClientInterface { + public: + virtual ~ImageConverterClientInterface() = default; + virtual std::string Convert(const std::string& input_file_path, + const std::string& output_file_path, + int image_quality, int image_size) = 0; +}; + +class ImageConverterClient : public ImageConverterClientInterface { + public: + ImageConverterClient() {} + + ~ImageConverterClient() override {} + + std::string Convert(const std::string& input_file_path, + const std::string& output_file_path, int image_quality, + int image_size) override { + LibRaw raw_processor; + Magick::Image magick_img; + Magick::Blob magick_blob; + + int ret = raw_processor.open_file(input_file_path.c_str()); + if (ret == LIBRAW_SUCCESS) { + raw_processor.imgdata.params.no_auto_bright = 1; + raw_processor.imgdata.params.use_camera_wb = 1; + raw_processor.imgdata.params.use_camera_matrix = 1; + raw_processor.imgdata.params.output_color = 1; + if (raw_processor.unpack() != LIBRAW_SUCCESS || + raw_processor.dcraw_process() != LIBRAW_SUCCESS) { + throw std::runtime_error("unpack or dcraw_process failed"); + } + + libraw_processed_image_t* image = + raw_processor.dcraw_make_mem_image(&ret); + if (!image) { + throw std::runtime_error("dcraw_make_mem_image failed"); + } + + SPDLOG_DEBUG("file_path {}", input_file_path); + + magick_blob = Magick::Blob(image->data, image->data_size); + magick_img = Magick::Image( + magick_blob, Magick::Geometry(image->width, image->height), 8, "RGB"); + + LibRaw::dcraw_clear_mem(image); + raw_processor.recycle(); + } else { + magick_img.read(input_file_path); + } + + int resize_width = magick_img.columns(), resize_height = magick_img.rows(); + if (image_size > 0) { + if (resize_width > resize_height) { + resize_height = + static_cast((image_size * resize_height) / resize_width); + resize_width = image_size; + } else { + resize_width = + static_cast((image_size * resize_width) / resize_height); + resize_height = image_size; + } + } + + SPDLOG_DEBUG("resizing image to {} {}", resize_width, resize_height); + + magick_img.resize(Magick::Geometry(resize_width, resize_height)); + magick_img.quality(image_quality); + magick_img.magick("JPEG"); + + if (output_file_path == "") { + magick_img.write(&magick_blob, "JPEG"); + const unsigned char* magick_blob_data_ptr = + static_cast(magick_blob.data()); + std::vector buffer( + magick_blob_data_ptr, magick_blob_data_ptr + magick_blob.length()); + return Base64Encode(buffer); + } + + magick_img.write(output_file_path); + return output_file_path; + } +}; + +class PreviewThumbnail { + public: + explicit PreviewThumbnail( + std::shared_ptr image_converter_client, + std::shared_ptr api_client, int image_quality = 50, + int thumbnail_size = 256, int placeholder_size = 2) + : image_converter_client_(image_converter_client), + api_client_(api_client), + image_quality_(image_quality), + thumbnail_size_(thumbnail_size), + placeholder_size_(placeholder_size) {} + std::unordered_map Generate( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& file_path, + const std::string& type); + + private: + std::shared_ptr image_converter_client_; + std::shared_ptr api_client_; + int image_quality_; + int thumbnail_size_; + int placeholder_size_; +}; + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client); + +} // namespace previewthumbnail + +} // namespace components diff --git a/worker/src/api_client.cc b/worker/src/api_client.cc new file mode 100644 index 00000000..cf0636fc --- /dev/null +++ b/worker/src/api_client.cc @@ -0,0 +1,127 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/api_client.h" + +#include +#include +#include + +#include +#include +#include + +#include "protos/api.grpc.pb.h" +#include "protos/api.pb.h" + +namespace services { + +namespace api { + +APIClient::APIClient(std::shared_ptr channel) + : stub_(API::NewStub(channel)) {} + +APIClient::APIClient(std::unique_ptr stub) + : stub_(std::move(stub)) {} + +std::string APIClient::GetWorkerConfig() { + google::protobuf::Empty request; + ConfigResponse response; + grpc::ClientContext context; + grpc::Status status = stub_->GetWorkerConfig(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error getting worker config: {}", status.error_message()); + return ""; + } + return response.config(); +} + +MediaItemProcessResponse APIClient::GetMediaItemProcess() { + google::protobuf::Empty request; + MediaItemProcessResponse response; + grpc::ClientContext context; + grpc::Status status = + stub_->GetMediaItemProcess(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error getting media item process: {}", + status.error_message()); + return {}; + } + return response; +} + +bool APIClient::SaveMediaItemMetadata(const MediaItemMetadataRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = + stub_->SaveMediaItemMetadata(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem metadata: {}", status.error_message()); + return false; + } + return true; +} + +bool APIClient::SaveMediaItemPreviewThumbnail( + const MediaItemPreviewThumbnailRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = + stub_->SaveMediaItemPreviewThumbnail(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem preview and thumbnail: {}", + status.error_message()); + return false; + } + return true; +} + +bool APIClient::SaveMediaItemPlace(const MediaItemPlaceRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = stub_->SaveMediaItemPlace(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem place: {}", status.error_message()); + return false; + } + return true; +} + +bool APIClient::SaveMediaItemFaces(const MediaItemFacesRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = stub_->SaveMediaItemFaces(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem places: {}", status.error_message()); + return false; + } + return true; +} + +bool APIClient::SaveMediaItemPeople(const MediaItemPeopleRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = + stub_->SaveMediaItemPeople(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem people: {}", status.error_message()); + return false; + } + return true; +} + +bool APIClient::SaveMediaItemFinalResult( + const MediaItemFinalResultRequest& request) { + google::protobuf::Empty response; + grpc::ClientContext context; + grpc::Status status = + stub_->SaveMediaItemFinalResult(&context, request, &response); + if (!status.ok()) { + SPDLOG_ERROR("error saving mediaitem final result: {}", + status.error_message()); + return false; + } + return true; +} + +} // namespace api + +} // namespace services diff --git a/worker/src/components.cc b/worker/src/components.cc new file mode 100644 index 00000000..88cb1d00 --- /dev/null +++ b/worker/src/components.cc @@ -0,0 +1,47 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/components.h" + +#include +#include + +#include +#include + +namespace components { + +ComponentConfig::ComponentConfig(const std::string& source, + const std::string& params) + : source(source), params(params) {} + +std::unordered_map ParseComponentConfig( + const std::string& config) { + if (config.empty()) { + SPDLOG_ERROR("empty component config"); + return {}; + } + std::unordered_map configs; + simdjson::ondemand::parser parser; + simdjson::padded_string padded_config = simdjson::padded_string(config); + simdjson::ondemand::document doc = parser.iterate(padded_config); + auto items = doc.get_array(); + if (items.error() != simdjson::SUCCESS) { + SPDLOG_ERROR("error parsing component config: {}", + simdjson::error_message(items.error())); + return {}; + } + for (auto item : items) { + std::string name = std::string(item["name"].get_string().value()); + std::string source = ""; + if (item["source"].error() == simdjson::SUCCESS) { + source = std::string(item["source"].get_string().value()); + } + std::string params = ""; + if (item["params"].error() == simdjson::SUCCESS) { + params = std::string(item["params"].get_string().value()); + } + configs.insert({name, ComponentConfig(source, params)}); + } + return configs; +} + +} // namespace components diff --git a/worker/src/main.cc b/worker/src/main.cc new file mode 100644 index 00000000..2436d645 --- /dev/null +++ b/worker/src/main.cc @@ -0,0 +1,115 @@ +// Copyright 2025 Omkar Prabhu +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef DEFAULT_VERSION +#define DEFAULT_VERSION "dev" +#endif +#ifndef DEFAULT_GIT_SHA +#define DEFAULT_GIT_SHA "-" +#endif + +#include "protos/api.pb.h" +#include "worker/api_client.h" +#include "worker/components.h" +#include "worker/config.h" +#include "worker/metadata.h" +#include "worker/places.h" +#include "worker/preview_thumbnail.h" + +using components::ComponentConfig; +using services::api::APIClient; + +constexpr const char kVersion[] = DEFAULT_VERSION; +constexpr const char kGitSha[] = DEFAULT_GIT_SHA; + +std::atomic terminating(false); + +void gracefulShutdown(int signum) { + SPDLOG_INFO("stopping worker"); + // TODO(omkar): clean up gRPC client + terminating = true; +} + +int main(int argc, char** argv) { + std::cout << "Version: " << kVersion << std::endl; + std::cout << "Git SHA: " << kGitSha << std::endl; + + auto cfg = std::make_unique(); + + auto stdout_sink = std::make_shared(); + auto logger = std::make_shared("", stdout_sink); + logger->set_level(cfg->log_level); + logger->set_pattern("%Y/%m/%d %H:%M:%S %l %v"); + spdlog::set_default_logger(logger); + + std::signal(SIGTERM, gracefulShutdown); + std::signal(SIGINT, gracefulShutdown); + + std::shared_ptr api_client = std::make_shared( + grpc::CreateChannel(cfg->api_host + ":" + cfg->api_port, + grpc::InsecureChannelCredentials())); + std::string worker_config = api_client->GetWorkerConfig(); + SPDLOG_INFO("worker config: {}", worker_config); + + std::shared_ptr metadata_component; + std::shared_ptr places_component; + std::shared_ptr + previewthumbnail_component; + + std::unordered_map component_configs = + components::ParseComponentConfig(worker_config); + for (const auto& [name, config] : component_configs) { + if (name == MediaItemComponent_Name(MediaItemComponent::METADATA)) { + metadata_component = components::metadata::Init(config, api_client); + } else if (name == MediaItemComponent_Name(MediaItemComponent::PLACES)) { + places_component = components::places::Init(config, api_client); + } else if (name == + MediaItemComponent_Name(MediaItemComponent::PREVIEW_THUMBNAIL)) { + Magick::InitializeMagick(*argv); + previewthumbnail_component = + components::previewthumbnail::Init(config, api_client); + } else if (name == MediaItemComponent_Name(MediaItemComponent::FACES)) { + // TODO(omkar): initialize this component + + } else if (name == MediaItemComponent_Name(MediaItemComponent::OCR)) { + // TODO(omkar): initialize this component + + } else if (name == MediaItemComponent_Name(MediaItemComponent::SEARCH)) { + // TODO(omkar): initialize this component + } + } + + while (!terminating) { + SPDLOG_INFO("worker running"); + sleep(2); + MediaItemProcessResponse response = api_client->GetMediaItemProcess(); + SPDLOG_INFO("id {}", response.id()); + if (response.id() != "") { + auto metadata_result = metadata_component->Extract( + response.id(), response.userid(), response.mediaitemid(), + response.payload().at("source_url")); + std::cout << metadata_result.size() << std::endl; + auto preview_thumbnail_result = previewthumbnail_component->Generate( + response.id(), response.userid(), response.mediaitemid(), + response.payload().at("source_url"), metadata_result["type"]); + std::cout << preview_thumbnail_result.size() << std::endl; + auto place_result = places_component->ReverseGeocode( + response.id(), response.userid(), response.mediaitemid(), + metadata_result["latitude"], metadata_result["longitude"]); + } + } + + return 0; +} diff --git a/worker/src/metadata.cc b/worker/src/metadata.cc new file mode 100644 index 00000000..83343ab1 --- /dev/null +++ b/worker/src/metadata.cc @@ -0,0 +1,218 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/metadata.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "protos/api.pb.h" +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace metadata { + +std::unordered_map Metadata::Extract( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& file_path) { + std::unordered_map result; + std::unordered_map exif_data; + + // default values + MediaItemStatus status = MediaItemStatus::PROCESSING; + MediaItemType type = MediaItemType::UNKNOWN; + MediaItemCategory category = MediaItemCategory::DEFAULT; + + MediaItemMetadataRequest request; + request.set_userid(user_id); + request.set_mediaitemid(mediaitem_id); + + try { + exif_data = exif_tool_client_->Extract(file_path); + + SPDLOG_DEBUG("exiftool response: {}", exif_data); + + std::string raw_exifdata = "{"; + for (auto it = exif_data.begin(); it != exif_data.end();) { + const std::string& key = it->first; + std::string value = it->second; + + // raw exif data + raw_exifdata.append("\""); + raw_exifdata.append(key); + raw_exifdata.append("\":\""); + raw_exifdata.append(value); + raw_exifdata.append("\""); + if (++it != exif_data.end()) { + raw_exifdata.append(","); + } + } + raw_exifdata.append("}"); + result["exifdata"] = raw_exifdata; + request.set_exifdata(result["exifdata"]); + + // other fields + result["mime_type"] = GetValue(exif_data, {"MIMEType"}); + request.set_mimetype(result["mime_type"]); + if (result["mime_type"].find("image") != std::string::npos) { + type = MediaItemType::PHOTO; + } else if (result["mime_type"].find("video") != std::string::npos) { + type = MediaItemType::VIDEO; + } + result["width"] = GetValue( + exif_data, {"ExifImageWidth", "ImageWidth", "SourceImageWidth"}); + result["height"] = GetValue( + exif_data, {"ExifImageHeight", "ImageHeight", "SourceImageHeight"}); + if (result["width"] != "") { + request.set_width(std::stoi(result["width"])); + } + if (result["height"] != "") { + request.set_height(std::stoi(result["height"])); + } + result["creation_time"] = + GetValue(exif_data, {"CreateDate", "DateCreated", "DateTimeOriginal"}); + std::regex date_re(R"((\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2}))"); + std::smatch date_match; + if (std::regex_search(result["creation_time"], date_match, date_re)) { + result["creation_time"] = + date_match[1].str() + "-" + date_match[2].str() + "-" + + date_match[3].str() + " " + date_match[4].str() + ":" + + date_match[5].str() + ":" + date_match[6].str(); + } + + // camera + result["camera_make"] = GetValue(exif_data, {"Make", "LensMake"}); + request.set_cameramake(result["camera_make"]); + + result["camera_model"] = GetValue(exif_data, {"Model", "LensModel"}); + request.set_cameramodel(result["camera_model"]); + + result["focal_length"] = GetValue(exif_data, {"FocalLength"}); + request.set_focallength(result["focal_length"]); + + result["aperture_fnumber"] = GetValue(exif_data, {"FNumber"}); + request.set_aperturefnumber(result["aperture_fnumber"]); + + result["iso_equivalent"] = GetValue(exif_data, {"ISO"}); + request.set_isoequivalent(result["iso_equivalent"]); + + result["exposure_time"] = GetValue(exif_data, {"ExposureTime"}); + request.set_exposuretime(result["exposure_time"]); + + result["fps"] = GetValue(exif_data, {"VideoFrameRate"}); + request.set_fps(result["fps"]); + + result["megapixels"] = GetValue(exif_data, {"Megapixels"}); + request.set_megapixels(result["megapixels"]); + + // gps + result["longitude"] = GetValue(exif_data, {"GPSLongitude"}); + if (result["longitude"] != "") { + result["longitude"] = GetCoordinates(result["longitude"]); + request.set_longitude(std::stod(result["longitude"])); + } + result["latitude"] = GetValue(exif_data, {"GPSLatitude"}); + if (result["latitude"] != "") { + result["latitude"] = GetCoordinates(result["latitude"]); + request.set_latitude(std::stod(result["latitude"])); + } + + // category + if (exif_data.find("UserComment") != exif_data.end()) { + std::string value = exif_data["UserComment"]; + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (value == "screenshot") { + category = MediaItemCategory::SCREENSHOT; + } + } else if (exif_data.find("LivePhotoVideoIndex") != exif_data.end()) { + category = MediaItemCategory::LIVE; + } else if (exif_data.find("FullFrameRatePlaybackIntent") != + exif_data.end()) { + if (exif_data["FullFrameRatePlaybackIntent"] == "0") { + category = MediaItemCategory::TIMELAPSE; + } + } else if (exif_data.find("CaptureMode") != exif_data.end()) { + std::string value = exif_data["CaptureMode"]; + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (value == "time-lapse") { + category = MediaItemCategory::TIMELAPSE; + } + } else if (result["width"] != "" && result["height"] != "") { + int width = std::stoi(result["width"]); + int height = std::stoi(result["height"]); + if (width >= 10000 && (height * 4 <= width)) { + category = MediaItemCategory::PANORAMA; + } + } else if (result["fps"] != "") { + int fps = std::stoi(result["fps"]); + if (fps >= 120) { + category = MediaItemCategory::SLOW; + } + } + } catch (const std::exception& e) { + SPDLOG_ERROR("error extracting metadata: {}", e.what()); + status = MediaItemStatus::FAILED; + } + + result["status"] = MediaItemStatus_Name(status); + result["type"] = MediaItemType_Name(type); + result["category"] = MediaItemCategory_Name(category); + + request.set_status(status); + request.set_type(type); + request.set_category(category); + api_client_->SaveMediaItemMetadata(request); + + return result; +} + +std::string GetCoordinates(const std::string& location) { + int degrees = 0, minutes = 0; + double seconds = 0.0; + char direction = 'N'; + double decimal = 0.0; + + if (std::sscanf(location.c_str(), "%d%*[^0-9]%d%*[^0-9]%lf%*[^NSEW]%c", + °rees, &minutes, &seconds, &direction) == 4) { + decimal = degrees + minutes / 60.0 + seconds / 3600.0; + if (direction == 'S' || direction == 'W') { + decimal = -decimal; + } + } + return std::to_string(decimal); +} + +std::string GetValue(const std::unordered_map& data, + const std::vector& keys) { + for (const std::string& key : keys) { + auto it = data.find(key); + if (it != data.end()) { + return it->second; // no double lookup + } + } + return ""; +} + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client) { + return std::make_shared(std::make_shared(), + api_client); +} + +} // namespace metadata + +} // namespace components diff --git a/worker/src/places.cc b/worker/src/places.cc new file mode 100644 index 00000000..9677dbc6 --- /dev/null +++ b/worker/src/places.cc @@ -0,0 +1,148 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/places.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace places { + +std::unordered_map Places::ReverseGeocode( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& latitude, + const std::string& longitude) { + return {}; +} + +std::unordered_map OpenStreetMap::ReverseGeocode( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& latitude, + const std::string& longitude) { + if (latitude == "" && longitude == "") { + return {}; + } + + SPDLOG_DEBUG("reverse geocoding with lat: {} and lon: {}", latitude, + longitude); + + cpr::Response r = http_client_->Get( + cpr::Url{Format(url_, {{"lat", latitude}, {"lon", longitude}})}, + cpr::Header{{"User-Agent", "smriti-worker"}, + {"Accept-Language", "en-GB,en-US"}}); + if (r.error.message != "") { + SPDLOG_ERROR("error in openstreetmap response: {}", r.error.message); + return {}; + } + if (r.status_code != 200) { + SPDLOG_ERROR("error in openstreetmap response: {} {}", r.status_code, + r.text); + return {}; + } + + SPDLOG_DEBUG("openstreetmap response: {} {}", r.status_code, r.text); + + simdjson::ondemand::parser parser; + simdjson::padded_string padded_body(r.text); + simdjson::ondemand::document doc = parser.iterate(padded_body); + + std::unordered_map result; + + if (doc["address"].error() == simdjson::SUCCESS) { + simdjson::ondemand::object address = doc["address"].get_object(); + + if (address["postcode"].error() == simdjson::SUCCESS) { + result["postcode"] = + std::string(address["postcode"].get_string().value()); + } + if (address["country"].error() == simdjson::SUCCESS) { + result["country"] = std::string(address["country"].get_string().value()); + } + + std::string locality = ""; + if (address["village"].error() == simdjson::SUCCESS) { + locality = std::string(address["village"].get_string().value()); + } + if (locality == "" && address["town"].error() == simdjson::SUCCESS) { + locality = std::string(address["town"].get_string().value()); + } + if (address["city"].error() == simdjson::SUCCESS) { + locality = std::string(address["city"].get_string().value()); + } + if (locality == "" && + address["municipality"].error() == simdjson::SUCCESS) { + locality = std::string(address["municipality"].get_string().value()); + } + if (locality == "" && address["county"].error() == simdjson::SUCCESS) { + locality = std::string(address["county"].get_string().value()); + } + result["locality"] = locality; + + std::string area; + if (address["suburb"].error() == simdjson::SUCCESS) { + area = std::string(address["suburb"].get_string().value()); + } + if (area == "" && address["quarter"].error() == simdjson::SUCCESS) { + area = std::string(address["quarter"].get_string().value()); + } + if (area == "" && address["neighbourhood"].error() == simdjson::SUCCESS) { + area = std::string(address["neighbourhood"].get_string().value()); + } + if (area == "" && address["road"].error() == simdjson::SUCCESS) { + area = std::string(address["road"].get_string().value()); + } + result["area"] = area; + } else { + SPDLOG_ERROR("error in openstreetmap response: {}", + simdjson::error_message(doc["address"].error())); + return {}; + } + + MediaItemPlaceRequest request; + request.set_userid(user_id); + request.set_mediaitemid(mediaitem_id); + request.set_postcode(result["postcode"]); + request.set_country(result["country"]); + request.set_locality(result["locality"]); + request.set_area(result["area"]); + api_client_->SaveMediaItemPlace(request); + + return result; +} + +std::string Format(std::string input, + const std::unordered_map& values) { + for (const auto& [key, value] : values) { + std::string placeholder = "{" + key + "}"; + size_t pos = input.find(placeholder); + if (pos != std::string::npos) { + input.replace(pos, placeholder.length(), value); + } + } + return input; +} + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client) { + if (config.source == "openstreetmap") { + return std::make_shared(std::make_shared(), + api_client); + } + return nullptr; +} + +} // namespace places + +} // namespace components diff --git a/worker/src/preview_thumbnail.cc b/worker/src/preview_thumbnail.cc new file mode 100644 index 00000000..aa00f672 --- /dev/null +++ b/worker/src/preview_thumbnail.cc @@ -0,0 +1,81 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/preview_thumbnail.h" + +#include +#include + +#include +#include +#include +#include + +#include "worker/api_client.h" +#include "worker/components.h" + +using services::api::APIClient; + +namespace components { + +namespace previewthumbnail { + +std::shared_ptr Init(const ComponentConfig& config, + std::shared_ptr api_client) { + simdjson::ondemand::parser parser; + simdjson::padded_string padded_config = + simdjson::padded_string(config.params); + simdjson::ondemand::document doc = parser.iterate(padded_config); + int image_quality = static_cast(doc["image_quality"].get_int64()); + int thumbnail_size = static_cast(doc["thumbnail_size"].get_int64()); + int placeholder_size = static_cast(doc["placeholder_size"].get_int64()); + return std::make_shared( + std::make_shared(), api_client, image_quality, + thumbnail_size, placeholder_size); +} + +std::unordered_map PreviewThumbnail::Generate( + const std::string& id, const std::string& user_id, + const std::string& mediaitem_id, const std::string& file_path, + const std::string& type) { + std::unordered_map result; + + // default value + MediaItemStatus status = MediaItemStatus::FAILED; + + MediaItemPreviewThumbnailRequest request; + request.set_userid(user_id); + request.set_mediaitemid(mediaitem_id); + request.set_sourcepath(file_path); + + try { + if (type == MediaItemType_Name(MediaItemType::PHOTO)) { + result["preview_url"] = image_converter_client_->Convert( + file_path, file_path + "-preview", image_quality_, 0); + } else if (type == MediaItemType_Name(MediaItemType::VIDEO)) { + result["preview_url"] = ""; + } + request.set_previewpath(result["preview_url"]); + + result["thumbnail_url"] = image_converter_client_->Convert( + result["preview_url"], file_path + "-thumbnail", image_quality_, + thumbnail_size_); + request.set_thumbnailpath(result["thumbnail_url"]); + + result["placeholder"] = image_converter_client_->Convert( + result["thumbnail_url"], "", image_quality_, placeholder_size_); + request.set_placeholder(result["placeholder"]); + + status = MediaItemStatus::READY; + } catch (const std::exception& e) { + SPDLOG_ERROR("error extracting preview thumbnail: {}", e.what()); + } + + result["status"] = MediaItemStatus_Name(status); + request.set_status(status); + api_client_->SaveMediaItemPreviewThumbnail(request); + + return result; +} + +} // namespace previewthumbnail + +} // namespace components diff --git a/worker/tests/CMakeLists.txt b/worker/tests/CMakeLists.txt new file mode 100644 index 00000000..7ede584a --- /dev/null +++ b/worker/tests/CMakeLists.txt @@ -0,0 +1,79 @@ +file(GLOB TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*_test.cc) + +file(GLOB EXEC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../src/*.cc) + +file(GLOB GENERATED_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../generated/protos/*.cc) + +list(FILTER EXEC_SOURCES EXCLUDE REGEX ".*/main\\.cc$") + +add_executable(worker_tests ${TEST_SOURCES} ${EXEC_SOURCES} ${GENERATED_SOURCES}) + +if(APPLE) + target_include_directories(worker_tests PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/include) + target_link_directories(worker_tests PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/lib) + target_link_libraries(worker_tests PRIVATE omp) +endif() + +target_include_directories(worker_tests + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../generated + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/simdjson/include + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/simdjson/src + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/cpr/include + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${IMAGEMAGICK_INCLUDE_DIRS} + ${LIBRAW_INCLUDE_DIRS} +) + +target_sources(worker_tests PRIVATE ${EXEC_SOURCES}) + +target_link_libraries(worker_tests PRIVATE + gtest_main + gmock_main + spdlog::spdlog + cpr::cpr + simdjson::simdjson + gRPC::grpc++ + gRPC::grpc + protobuf::libprotobuf + ImageMagick::Magick++ + ${LIBRAW_LIBRARIES}) + +include(GoogleTest) + +gtest_discover_tests(worker_tests) + +file(GLOB BENCHMARK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*_benchmark.cc) + +add_executable(worker_benchmarks ${BENCHMARK_SOURCES}) + +if(APPLE) + target_include_directories(worker_benchmarks PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/include) + target_link_directories(worker_benchmarks PRIVATE ${HOMEBREW_PREFIX}/opt/libomp/lib) + target_link_libraries(worker_benchmarks PRIVATE omp) +endif() + +target_include_directories(worker_benchmarks + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../generated + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/simdjson/include + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/simdjson/src + ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/cpr/include + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${IMAGEMAGICK_INCLUDE_DIRS} + ${LIBRAW_INCLUDE_DIRS} +) + +target_sources(worker_benchmarks PRIVATE ${EXEC_SOURCES} ${EXEC_SOURCES} ${GENERATED_SOURCES}) + +target_link_libraries(worker_benchmarks PRIVATE + benchmark_main + gmock_main + spdlog::spdlog + cpr::cpr + simdjson::simdjson + gRPC::grpc++ + gRPC::grpc + protobuf::libprotobuf + ImageMagick::Magick++ + ${LIBRAW_LIBRARIES}) \ No newline at end of file diff --git a/worker/tests/api_client_test.cc b/worker/tests/api_client_test.cc new file mode 100644 index 00000000..69e5fdea --- /dev/null +++ b/worker/tests/api_client_test.cc @@ -0,0 +1,203 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/api_client.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" + +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +class APIClientTest : public ::testing::Test { + protected: + void SetUp() override { + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + mock_stub_ = mock_api_stub.get(); + client_ = std::make_unique(std::move(mock_api_stub)); + } + + MockAPIStub* mock_stub_; + std::unique_ptr client_; +}; + +TEST_F(APIClientTest, GetWorkerConfigSuccess) { + EXPECT_CALL(*mock_stub_, GetWorkerConfig(_, _, _)) + .WillOnce(Invoke([&](grpc::ClientContext*, const google::protobuf::Empty&, + ConfigResponse* resp) { + ConfigResponse response; + response.set_config("worker-config"); + *resp = response; + return grpc::Status::OK; + })); + std::string config = client_->GetWorkerConfig(); + EXPECT_EQ(config, "worker-config"); +} + +TEST_F(APIClientTest, GetWorkerConfigFailure) { + EXPECT_CALL(*mock_stub_, GetWorkerConfig(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + std::string config = client_->GetWorkerConfig(); + EXPECT_EQ(config, ""); +} + +TEST_F(APIClientTest, GetMediaItemProcessSuccess) { + EXPECT_CALL(*mock_stub_, GetMediaItemProcess(_, _, _)) + .WillOnce(Invoke([&](grpc::ClientContext*, const google::protobuf::Empty&, + MediaItemProcessResponse* resp) { + MediaItemProcessResponse response; + response.set_id("id"); + response.set_mediaitemid("mediaitem-id"); + response.add_components(MediaItemComponent::METADATA); + response.add_components(MediaItemComponent::PREVIEW_THUMBNAIL); + (*response.mutable_payload())["key"] = "value"; + *resp = response; + return grpc::Status::OK; + })); + MediaItemProcessResponse mediaitem_process = client_->GetMediaItemProcess(); + EXPECT_EQ(mediaitem_process.id(), "id"); + EXPECT_EQ(mediaitem_process.mediaitemid(), "mediaitem-id"); + ASSERT_EQ(mediaitem_process.components_size(), 2); + EXPECT_EQ(mediaitem_process.components(0), MediaItemComponent::METADATA); + EXPECT_EQ(mediaitem_process.components(1), + MediaItemComponent::PREVIEW_THUMBNAIL); + ASSERT_EQ(mediaitem_process.payload().size(), 1); + EXPECT_EQ(mediaitem_process.payload().at("key"), "value"); +} + +TEST_F(APIClientTest, GetMediaItemProcessFailure) { + EXPECT_CALL(*mock_stub_, GetMediaItemProcess(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemProcessResponse mediaitem_process = client_->GetMediaItemProcess(); + EXPECT_TRUE(mediaitem_process.id().empty()); + EXPECT_TRUE(mediaitem_process.mediaitemid().empty()); + EXPECT_EQ(mediaitem_process.components_size(), 0); + EXPECT_EQ(mediaitem_process.payload().size(), 0); +} + +TEST_F(APIClientTest, SaveMediaItemMetadataSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemMetadata(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemMetadataRequest request; + bool ok = client_->SaveMediaItemMetadata(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemMetadataFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemMetadata(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemMetadataRequest request; + bool ok = client_->SaveMediaItemMetadata(request); + EXPECT_FALSE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPreviewThumbnailSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillOnce(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemPreviewThumbnailRequest request; + bool ok = client_->SaveMediaItemPreviewThumbnail(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPreviewThumbnailFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemPreviewThumbnailRequest request; + bool ok = client_->SaveMediaItemPreviewThumbnail(request); + EXPECT_FALSE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPlaceSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPlace(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemPlaceRequest request; + bool ok = client_->SaveMediaItemPlace(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPlaceFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPlace(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemPlaceRequest request; + bool ok = client_->SaveMediaItemPlace(request); + EXPECT_FALSE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemFacesSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemFaces(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemFacesRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemFacesRequest request; + bool ok = client_->SaveMediaItemFaces(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemFacesFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemFaces(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemFacesRequest request; + bool ok = client_->SaveMediaItemFaces(request); + EXPECT_FALSE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPeopleSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPeople(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemPeopleRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemPeopleRequest request; + bool ok = client_->SaveMediaItemPeople(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemPeopleFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemPeople(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemPeopleRequest request; + bool ok = client_->SaveMediaItemPeople(request); + EXPECT_FALSE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemFinalResultSuccess) { + EXPECT_CALL(*mock_stub_, SaveMediaItemFinalResult(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemFinalResultRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + MediaItemFinalResultRequest request; + bool ok = client_->SaveMediaItemFinalResult(request); + EXPECT_TRUE(ok); +} + +TEST_F(APIClientTest, SaveMediaItemFinalResultFailure) { + EXPECT_CALL(*mock_stub_, SaveMediaItemFinalResult(_, _, _)) + .WillOnce(Return( + grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service unavailable"))); + MediaItemFinalResultRequest request; + bool ok = client_->SaveMediaItemFinalResult(request); + EXPECT_FALSE(ok); +} diff --git a/worker/tests/components_benchmark.cc b/worker/tests/components_benchmark.cc new file mode 100644 index 00000000..b31ae181 --- /dev/null +++ b/worker/tests/components_benchmark.cc @@ -0,0 +1,28 @@ +// Copyright 2025 Omkar Prabhu +#include +#include + +#include +#include + +#include "worker/components.h" + +using components::ComponentConfig; + +static void BM_ComponentsParseComponentConfig( + benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::string config = R"([ + {"name":"component1"}, + {"name":"component2","source":"source2"}, + {"name":"component3","source":"source3","params":"params3"} +])"; + for (auto _ : state) { + std::unordered_map result = + components::ParseComponentConfig(config); + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK(BM_ComponentsParseComponentConfig)->ThreadPerCpu(); +BENCHMARK_MAIN(); diff --git a/worker/tests/components_test.cc b/worker/tests/components_test.cc new file mode 100644 index 00000000..7566642b --- /dev/null +++ b/worker/tests/components_test.cc @@ -0,0 +1,39 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/components.h" + +#include +#include + +#include +#include + +using components::ComponentConfig; + +TEST(ComponentsTest, ParseComponentConfigSuccess) { + spdlog::set_level(spdlog::level::off); + std::string config = R"([ + {"name":"component1"}, + {"name":"component2","source":"source2"}, + {"name":"component3","source":"source3","params":"params3"} +])"; + std::unordered_map result = + components::ParseComponentConfig(config); + ASSERT_EQ(result.size(), 3); + EXPECT_TRUE(result.at("component1").source.empty()); + EXPECT_TRUE(result.at("component1").params.empty()); + EXPECT_EQ(result.at("component2").source, "source2"); + EXPECT_TRUE(result.at("component2").params.empty()); + EXPECT_EQ(result.at("component3").source, "source3"); + EXPECT_EQ(result.at("component3").params, "params3"); +} + +TEST(ComponentsTest, ParseComponentConfigFailure) { + spdlog::set_level(spdlog::level::off); + std::string config = R"()"; + std::unordered_map result = + components::ParseComponentConfig(config); + ASSERT_EQ(result.size(), 0); + config = R"([])"; + result = components::ParseComponentConfig(config); + ASSERT_EQ(result.size(), 0); +} diff --git a/worker/tests/metadata_benchmark.cc b/worker/tests/metadata_benchmark.cc new file mode 100644 index 00000000..8c331b8a --- /dev/null +++ b/worker/tests/metadata_benchmark.cc @@ -0,0 +1,124 @@ +// Copyright 2025 Omkar Prabhu +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "metadata_test.cc" // NOLINT +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" +#include "worker/metadata.h" + +using components::ComponentConfig; +using components::metadata::ExifToolClientInterface; +using components::metadata::Metadata; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +static void BM_MetadataInit(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + ComponentConfig config = ComponentConfig("", ""); + for (auto _ : state) { + auto metadata = components::metadata::Init(config, nullptr); + benchmark::DoNotOptimize(metadata); + } +} + +static void BM_MetadataEmptyData(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + std::unordered_map mock_data; + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map output = + metadata.Extract("", "", "", ""); + benchmark::DoNotOptimize(output); + } +} + +static void BM_MetadataFailure(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillRepeatedly(::testing::Throw(std::runtime_error("some error"))); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map output = + metadata.Extract("", "", "", ""); + benchmark::DoNotOptimize(output); + } +} + +static void BM_MetadataSuccess(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + std::unordered_map mock_data = { + {"VideoFrameRate", "30"}, + {"GPSLatitude", "19 deg 13' 11.99\" N"}, + {"GPSLongitude", "73 deg 6' 19.19\" E"}, + {"ExifImageWidth", "4032"}, + {"ImageWidth", "226"}, + {"ExifImageHeight", "3024"}, + {"ImageHeight", "4032"}, + {"Make", "Apple"}, + {"Model", "iPhone 15 Pro"}, + {"FocalLength", "4.2 mm"}, + {"FNumber", "1.6"}, + {"ISO", "640"}, + {"Megapixels", "24"}, + {"MIMEType", "image/heic"}, + {"ExposureTime", "1/25"}, + {"LivePhotoVideoIndex", "1112547328"}, + {"DateCreated", "2022:04:03 12:56:11"}}; + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map output = + metadata.Extract("", "", "", ""); + benchmark::DoNotOptimize(output); + } +} + +BENCHMARK(BM_MetadataInit)->ThreadPerCpu(); +BENCHMARK(BM_MetadataEmptyData)->ThreadPerCpu(); +BENCHMARK(BM_MetadataFailure)->ThreadPerCpu(); +BENCHMARK(BM_MetadataSuccess)->ThreadPerCpu(); diff --git a/worker/tests/metadata_test.cc b/worker/tests/metadata_test.cc new file mode 100644 index 00000000..fbbc9a18 --- /dev/null +++ b/worker/tests/metadata_test.cc @@ -0,0 +1,176 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/metadata.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" + +using components::ComponentConfig; +using components::metadata::ExifToolClientInterface; +using components::metadata::Metadata; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +class MockExifToolClient : public ExifToolClientInterface { + public: + MOCK_METHOD((std::unordered_map), Extract, + (const std::string& file_path), (override)); +}; + +void assertMetadataResult(std::unordered_map expected, + std::unordered_map actual) { + EXPECT_EQ(expected.size(), actual.size()); + for (const auto& [key, value] : expected) { + EXPECT_EQ(value, actual[key]); + } +} + +TEST(MetadataTest, Init) { + spdlog::set_level(spdlog::level::off); + auto metadata = components::metadata::Init(ComponentConfig("", ""), nullptr); + ASSERT_TRUE(metadata != nullptr); +} + +TEST(MetadataTest, EmptyData) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + std::unordered_map mock_data; + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillOnce(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map result = + metadata.Extract("", "", "", ""); + assertMetadataResult( + { + {"status", "PROCESSING"}, + {"type", "UNKNOWN"}, + {"category", "DEFAULT"}, + {"latitude", ""}, + {"longitude", ""}, + {"fps", ""}, + {"height", ""}, + {"width", ""}, + {"camera_make", ""}, + {"camera_model", ""}, + {"focal_length", ""}, + {"aperture_fnumber", ""}, + {"iso_equivalent", ""}, + {"exposure_time", ""}, + {"mime_type", ""}, + {"creation_time", ""}, + {"megapixels", ""}, + {"exifdata", "{}"}, + }, + result); +} + +TEST(MetadataTest, Failure) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillOnce(::testing::Throw(std::runtime_error("some error"))); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map result = + metadata.Extract("", "", "", ""); + assertMetadataResult( + {{"status", "FAILED"}, {"type", "UNKNOWN"}, {"category", "DEFAULT"}}, + result); +} + +TEST(MetadataTest, Success) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_exif_client = + std::make_shared(); + std::unordered_map mock_data = { + {"VideoFrameRate", "30"}, + {"GPSLatitude", "19 deg 13' 11.99\" N"}, + {"GPSLongitude", "73 deg 6' 19.19\" E"}, + {"ExifImageWidth", "4032"}, + {"ImageWidth", "226"}, + {"ExifImageHeight", "3024"}, + {"ImageHeight", "4032"}, + {"Make", "Apple"}, + {"Model", "iPhone 15 Pro"}, + {"FocalLength", "4.2 mm"}, + {"FNumber", "1.6"}, + {"ISO", "640"}, + {"Megapixels", "24"}, + {"MIMEType", "image/heic"}, + {"ExposureTime", "1/25"}, + {"LivePhotoVideoIndex", "1112547328"}, + {"DateCreated", "2022:04:03 12:56:11"}}; + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemMetadata(_, _, _)) + .WillOnce( + Invoke([&](grpc::ClientContext*, const MediaItemMetadataRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + EXPECT_CALL(*mock_exif_client, Extract(::testing::_)) + .WillOnce(::testing::Return(mock_data)); + Metadata metadata(mock_exif_client, mock_api_client); + std::unordered_map result = + metadata.Extract("", "", "", ""); + assertMetadataResult( + {{"status", "PROCESSING"}, + {"type", "PHOTO"}, + {"category", "LIVE"}, + {"latitude", "19.219997"}, + {"longitude", "73.105331"}, + {"fps", "30"}, + {"height", "3024"}, + {"width", "4032"}, + {"camera_make", "Apple"}, + {"camera_model", "iPhone 15 Pro"}, + {"focal_length", "4.2 mm"}, + {"aperture_fnumber", "1.6"}, + {"iso_equivalent", "640"}, + {"exposure_time", "1/25"}, + {"mime_type", "image/heic"}, + {"megapixels", "24"}, + {"creation_time", "2022-04-03 12:56:11"}, + {"exifdata", + "{\"VideoFrameRate\":\"30\",\"ExifImageWidth\":" + "\"4032\",\"GPSLatitude\":\"19 deg 13' 11.99\" " + "N\",\"GPSLongitude\":\"73 deg 6' 19.19\" " + "E\",\"ImageWidth\":\"226\",\"Make\":\"Apple\"," + "\"Model\":\"iPhone 15 Pro\",\"FocalLength\":\"4.2 " + "mm\",\"ExifImageHeight\":\"3024\",\"FNumber\":\"1." + "6\",\"ImageHeight\":\"4032\",\"ISO\":\"640\",\"Megapixels\":\"24\"," + "\"MIMEType\":\"image/heic\",\"ExposureTime\":\"1/" + "25\",\"LivePhotoVideoIndex\":\"1112547328\"," + "\"DateCreated\":\"2022:04:03 12:56:11\"}"}}, + result); +} diff --git a/worker/tests/places_benchmark.cc b/worker/tests/places_benchmark.cc new file mode 100644 index 00000000..7d5eec41 --- /dev/null +++ b/worker/tests/places_benchmark.cc @@ -0,0 +1,143 @@ +// Copyright 2025 Omkar Prabhu +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "places_test.cc" // NOLINT +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" +#include "worker/places.h" + +using components::ComponentConfig; +using components::places::HttpClientInterface; +using components::places::OpenStreetMap; +using components::places::Places; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +static void BM_PlacesInit(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + ComponentConfig config = ComponentConfig("openstreetmap", "params"); + for (auto _ : state) { + auto places = components::places::Init(config, nullptr); + benchmark::DoNotOptimize(places); + } +} + +static void BM_PlacesEmptyInput(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + for (auto _ : state) { + Places places; + std::unordered_map output = + places.ReverseGeocode("", "", "", "", ""); + benchmark::DoNotOptimize(output); + } +} + +static void BM_OpenStreetMapEmptyInput(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + for (auto _ : state) { + Places places; + std::unordered_map output = + places.ReverseGeocode("", "", "", "", ""); + benchmark::DoNotOptimize(output); + } +} + +static void BM_OpenStreetMapErrorResponse(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + for (auto _ : state) { + OpenStreetMap osm(mock_http_client, mock_api_client); + cpr::Response mock_response; + mock_response.error.message = "mock error"; + mock_response.status_code = 200; + mock_response.text = "{}"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + std::unordered_map output = + osm.ReverseGeocode("", "", "", "1.23", "4.56"); + benchmark::DoNotOptimize(output); + } +} + +static void BM_OpenStreetMapIncorrectStatusCode( + benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + for (auto _ : state) { + OpenStreetMap osm(mock_http_client, mock_api_client); + cpr::Response mock_response; + mock_response.status_code = 500; + mock_response.text = "{}"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + std::unordered_map output = + osm.ReverseGeocode("", "", "", "0.0", "0.0"); + benchmark::DoNotOptimize(output); + } +} + +static void BM_OpenStreetMapSuccess(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + for (auto _ : state) { + OpenStreetMap osm(mock_http_client, mock_api_client); + cpr::Response mock_response; + mock_response.status_code = 200; + mock_response.text = + R"({"address":{"suburb":"K/W Ward","city":"Mumbai","state":"Maharashtra","postcode":"402205","country":"India"}})"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + std::unordered_map output = + osm.ReverseGeocode("", "", "", "1.23", "4.56"); + benchmark::DoNotOptimize(output); + } +} + +BENCHMARK(BM_PlacesInit)->ThreadPerCpu(); +BENCHMARK(BM_PlacesEmptyInput)->ThreadPerCpu(); +BENCHMARK(BM_OpenStreetMapEmptyInput)->ThreadPerCpu(); +BENCHMARK(BM_OpenStreetMapErrorResponse)->ThreadPerCpu(); +BENCHMARK(BM_OpenStreetMapIncorrectStatusCode)->ThreadPerCpu(); +BENCHMARK(BM_OpenStreetMapSuccess)->ThreadPerCpu(); diff --git a/worker/tests/places_test.cc b/worker/tests/places_test.cc new file mode 100644 index 00000000..29ed79fc --- /dev/null +++ b/worker/tests/places_test.cc @@ -0,0 +1,158 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/places.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" + +using components::ComponentConfig; +using components::places::HttpClientInterface; +using components::places::OpenStreetMap; +using components::places::Places; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +class MockHttpClient : public HttpClientInterface { + public: + MOCK_METHOD(cpr::Response, Get, + (const cpr::Url& url, const cpr::Header& headers), (override)); +}; + +void assertPlacesResult(std::unordered_map expected, + std::unordered_map actual) { + EXPECT_EQ(expected.size(), actual.size()); + for (const auto& [key, value] : expected) { + EXPECT_EQ(value, actual[key]); + } +} + +TEST(PlacesTest, Init) { + spdlog::set_level(spdlog::level::off); + auto places = components::places::Init( + ComponentConfig("openstreetmap", "params"), nullptr); + ASSERT_TRUE(places != nullptr); + auto osm = std::dynamic_pointer_cast(places); + ASSERT_TRUE(osm != nullptr); + places = + components::places::Init(ComponentConfig("unknown", "params"), nullptr); + ASSERT_TRUE(places == nullptr); +} + +TEST(PlacesTest, EmptyInput) { + spdlog::set_level(spdlog::level::off); + Places places; + std::unordered_map result = + places.ReverseGeocode("", "", "", "", ""); + assertPlacesResult({}, result); +} + +TEST(OpenStreetMapTest, EmptyInput) { + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + OpenStreetMap osm(mock_http_client, mock_api_client); + std::unordered_map result = + osm.ReverseGeocode("", "", "", "", ""); + assertPlacesResult({}, result); +} + +TEST(OpenStreetMapTest, ErrorResponse) { + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + cpr::Response mock_response; + mock_response.error.message = "mock error"; + mock_response.status_code = 200; + mock_response.text = "{}"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + OpenStreetMap osm(mock_http_client, mock_api_client); + std::unordered_map result = + osm.ReverseGeocode("", "", "", "1.23", "4.56"); + assertPlacesResult({}, result); +} + +TEST(OpenStreetMapTest, IncorrectStatusCode) { + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + cpr::Response mock_response; + mock_response.status_code = 500; + mock_response.text = "{}"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + OpenStreetMap osm(mock_http_client, mock_api_client); + std::unordered_map result = + osm.ReverseGeocode("", "", "", "0.0", "0.0"); + assertPlacesResult({}, result); +} + +TEST(OpenStreetMapTest, Success) { + spdlog::set_level(spdlog::level::off); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPlace(_, _, _)) + .WillRepeatedly( + Invoke([&](grpc::ClientContext*, const MediaItemPlaceRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + std::shared_ptr mock_http_client = + std::make_shared(); + cpr::Response mock_response; + mock_response.status_code = 200; + mock_response.text = + R"({"address":{"suburb":"K/W Ward","city":"Mumbai","state":"Maharashtra","postcode":"402205","country":"India"}})"; + EXPECT_CALL(*mock_http_client, Get(::testing::_, ::testing::_)) + .WillOnce(::testing::Return(mock_response)); + OpenStreetMap osm(mock_http_client, mock_api_client); + std::unordered_map result = + osm.ReverseGeocode("", "", "", "1.23", "4.56"); + assertPlacesResult( + { + {"postcode", "402205"}, + {"country", "India"}, + {"locality", "Mumbai"}, + {"area", "K/W Ward"}, + }, + result); +} diff --git a/worker/tests/preview_thumbnail_benchmark.cc b/worker/tests/preview_thumbnail_benchmark.cc new file mode 100644 index 00000000..c9ba0b60 --- /dev/null +++ b/worker/tests/preview_thumbnail_benchmark.cc @@ -0,0 +1,123 @@ +// Copyright 2025 Omkar Prabhu +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "preview_thumbnail_test.cc" // NOLINT +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" +#include "worker/preview_thumbnail.h" + +using components::ComponentConfig; +using components::previewthumbnail::ImageConverterClientInterface; +using components::previewthumbnail::PreviewThumbnail; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +static void BM_PreviewThumbnailInit(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + ComponentConfig config = + ComponentConfig("", + "{\"image_quality\":50,\"thumbnail_size\":256," + "\"placeholder_size\":2}"); + for (auto _ : state) { + auto previewthumbnail = components::previewthumbnail::Init( + ComponentConfig("", + "{\"image_quality\":50,\"thumbnail_size\":256," + "\"placeholder_size\":2}"), + nullptr); + benchmark::DoNotOptimize(previewthumbnail); + } +} + +static void BM_PreviewThumbnailFailure(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Throw(std::runtime_error("some error"))); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillRepeatedly(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map output = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::PHOTO)); + benchmark::DoNotOptimize(output); + } +} + +static void BM_PreviewThumbnailPhotoSuccess(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + std::string mock_data = "path-kind"; + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillRepeatedly(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map output = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::PHOTO)); + benchmark::DoNotOptimize(output); + } +} + +static void BM_PreviewThumbnailVideoSuccess(benchmark::State& state) { // NOLINT + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + std::string mock_data = "path-kind"; + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillRepeatedly(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + for (auto _ : state) { + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map output = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::VIDEO)); + benchmark::DoNotOptimize(output); + } +} + +BENCHMARK(BM_PreviewThumbnailInit)->ThreadPerCpu(); +BENCHMARK(BM_PreviewThumbnailFailure)->ThreadPerCpu(); +BENCHMARK(BM_PreviewThumbnailPhotoSuccess)->ThreadPerCpu(); +BENCHMARK(BM_PreviewThumbnailVideoSuccess)->ThreadPerCpu(); diff --git a/worker/tests/preview_thumbnail_test.cc b/worker/tests/preview_thumbnail_test.cc new file mode 100644 index 00000000..48273de3 --- /dev/null +++ b/worker/tests/preview_thumbnail_test.cc @@ -0,0 +1,132 @@ +// Copyright 2025 Omkar Prabhu +#include "worker/preview_thumbnail.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "protos/api.pb.h" +#include "protos/api_mock.grpc.pb.h" +#include "worker/components.h" + +using components::ComponentConfig; +using components::previewthumbnail::ImageConverterClientInterface; +using components::previewthumbnail::PreviewThumbnail; +using services::api::APIClient; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; + +class MockImageConverterClient : public ImageConverterClientInterface { + public: + MOCK_METHOD((std::string), Convert, + (const std::string& input_file_path, + const std::string& output_file_path, int image_quality, + int image_size), + (override)); +}; + +void assertPreviewThumbnailResult( + std::unordered_map expected, + std::unordered_map actual) { + EXPECT_EQ(expected.size(), actual.size()); + for (const auto& [key, value] : expected) { + EXPECT_EQ(value, actual[key]); + } +} + +TEST(PreviewThumbnailTest, Init) { + spdlog::set_level(spdlog::level::off); + auto previewthumbnail = components::previewthumbnail::Init( + ComponentConfig("", + "{\"image_quality\":50,\"thumbnail_size\":256," + "\"placeholder_size\":2}"), + nullptr); + ASSERT_TRUE(previewthumbnail != nullptr); +} + +TEST(PreviewThumbnailTest, Failure) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Throw(std::runtime_error("some error"))); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillOnce(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map result = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::PHOTO)); + assertPreviewThumbnailResult({{"status", "FAILED"}}, result); +} + +TEST(PreviewThumbnailTest, PhotoSuccess) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + std::string mock_data = "path-kind"; + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillOnce(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map result = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::PHOTO)); + assertPreviewThumbnailResult({{"preview_url", "path-kind"}, + {"thumbnail_url", "path-kind"}, + {"placeholder", "path-kind"}, + {"status", "READY"}}, + result); +} + +TEST(PreviewThumbnailTest, VideoSuccess) { + spdlog::set_level(spdlog::level::off); + std::shared_ptr mock_image_converter_client = + std::make_shared(); + std::string mock_data = "path-kind"; + EXPECT_CALL(*mock_image_converter_client, + Convert(::testing::_, ::testing::_, ::testing::_, ::testing::_)) + .WillRepeatedly(::testing::Return(mock_data)); + auto mock_api_stub = std::make_unique>(); + MockAPIStub* mock_stub = mock_api_stub.get(); + std::shared_ptr mock_api_client = + std::make_shared(std::move(mock_api_stub)); + EXPECT_CALL(*mock_stub, SaveMediaItemPreviewThumbnail(_, _, _)) + .WillOnce(Invoke( + [&](grpc::ClientContext*, const MediaItemPreviewThumbnailRequest&, + google::protobuf::Empty*) { return grpc::Status::OK; })); + PreviewThumbnail previewthumbnail(mock_image_converter_client, + mock_api_client); + std::unordered_map result = + previewthumbnail.Generate("", "", "", "", + MediaItemType_Name(MediaItemType::VIDEO)); + assertPreviewThumbnailResult({{"preview_url", ""}, + {"thumbnail_url", "path-kind"}, + {"placeholder", "path-kind"}, + {"status", "READY"}}, + result); +}