diff --git a/.github/assignees.yml b/.github/assignees.yml index 717af2a0c..05dbfc03a 100644 --- a/.github/assignees.yml +++ b/.github/assignees.yml @@ -1 +1,2 @@ addAssignees: author +runOnDraft: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9aa5f33ca..365d9b13b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,18 @@ version: 2 updates: - package-ecosystem: gomod - directory: / + directory: ./backend/ + schedule: + interval: weekly + - package-ecosystem: gomod + directory: ./cli/ + schedule: + interval: weekly + - package-ecosystem: npm + directory: ./frontend/sac-mobile + schedule: + interval: weekly + - package-ecosystem: npm + directory: ./frontend/sac-web schedule: interval: weekly diff --git a/.github/workflows/auto_assign_author.yml b/.github/workflows/auto_assign_author.yml index 65f25a9ff..00512ecf4 100644 --- a/.github/workflows/auto_assign_author.yml +++ b/.github/workflows/auto_assign_author.yml @@ -2,11 +2,16 @@ name: Auto Assign Author on: pull_request: - types: [opened, ready_for_review, reopened] + types: [opened, reopened] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: auto-add-assignee: runs-on: ubuntu-latest + if: github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]' steps: - uses: kentaro-m/auto-assign-action@v1.2.6 with: diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml index 0f5eda810..797b2771b 100644 --- a/.github/workflows/auto_request_review.yml +++ b/.github/workflows/auto_request_review.yml @@ -2,11 +2,15 @@ name: Auto Request Review on: pull_request: - types: [opened, ready_for_review, reopened] + types: [opened, reopened] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: auto-request-review: runs-on: ubuntu-latest + if: github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]' steps: - name: Request review from the TLs and random team members uses: necojackarc/auto-request-review@v0.12.0 diff --git a/.github/workflows/go.yml b/.github/workflows/backend.yml similarity index 53% rename from .github/workflows/go.yml rename to .github/workflows/backend.yml index f17f30527..4310d47fe 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/backend.yml @@ -1,14 +1,15 @@ -name: Go +name: Backend on: push: - branches: - - main + paths: + - "backend/**" + - ".github/workflows/backend.yml" pull_request: + types: opened paths: - "backend/**" - - "scripts/init_db.sh" - - ".github/workflows/go.yml" + - ".github/workflows/backend.yml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,28 +26,41 @@ jobs: uses: actions/setup-go@v3 with: go-version: "1.21" - - name: Enforce formatting - run: gofmt -l ./backend/ | grep ".go$" | xargs -r echo "Files not formatted:" - + - name: Cache Go Modules + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Install gofumpt + run: go install mvdan.cc/gofumpt@latest + - name: Check code formatting + run: | + unformatted_files=$(gofumpt -l ./backend/) + if [ -n "$unformatted_files" ]; then + echo "Files not formatted:" + echo "$unformatted_files" + exit 1 + fi lint: name: Lint runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + checks: write steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 with: go-version: "1.21" - - name: Enforce linting - run: | - cd backend && lint_output=$(go vet ./...) - if [[ -n "$lint_output" ]]; then - echo "$lint_output" - echo "::error::Linting issues found" - exit 1 - fi - + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: ./backend/ test: name: Test runs-on: ubuntu-latest @@ -66,8 +80,15 @@ jobs: uses: actions/setup-go@v3 with: go-version: "1.21" + - name: Cache Go Modules + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Install Dependencies - run: cd backend && go get ./... + run: cd ./backend/ && go get ./... - name: Increase max_connections in PostgreSQL run: | CONTAINER_ID=$(docker ps --filter "publish=5432" --format "{{.ID}}") @@ -75,9 +96,7 @@ jobs: docker exec $CONTAINER_ID cat /var/lib/postgresql/data/postgresql.conf | grep max_connections - name: Restart PostgreSQL Container run: docker restart $(docker ps --filter "publish=5432" --format "{{.ID}}") - - name: Migrate DB - run: cd backend/src && go run main.go --only-migrate - name: Run Tests with Coverage - run: cd backend && go test -failfast -benchmem -race -coverprofile=coverage.txt ./... + run: cd ./backend/ && go test -bench=. -benchmem -race -coverprofile=coverage.txt ./... - name: Print Coverage - run: cd backend && go tool cover -func=coverage.txt + run: cd ./backend/ && go tool cover -func=coverage.txt diff --git a/.github/workflows/backend_codeql.yml b/.github/workflows/backend_codeql.yml new file mode 100644 index 000000000..9e394fc1a --- /dev/null +++ b/.github/workflows/backend_codeql.yml @@ -0,0 +1,44 @@ +name: Backend CodeQL + +on: + push: + paths: + - "backend/**" + - ".github/workflows/backend_codeql.yml" + pull_request: + types: opened + paths: + - "backend/**" + - ".github/workflows/backend_codeql.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.21" + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "go" + queries: security-and-quality + - name: Build + run: | + cd ./backend/ && go build -o backend src/main.go + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:go" diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml new file mode 100644 index 000000000..6d7da6357 --- /dev/null +++ b/.github/workflows/cli.yml @@ -0,0 +1,63 @@ +name: CLI + +on: + push: + paths: + - "cli/**" + - ".github/workflows/cli.yml" + pull_request: + types: opened + paths: + - "cli/**" + - ".github/workflows/cli.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + format: + name: Format + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.21" + - name: Cache Go Modules + uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Install gofumpt + run: go install mvdan.cc/gofumpt@latest + - name: Check code formatting + run: | + unformatted_files=$(gofumpt -l ./cli/) + if [ -n "$unformatted_files" ]; then + echo "Files not formatted:" + echo "$unformatted_files" + exit 1 + fi + lint: + name: Lint + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + checks: write + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: "1.21" + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + working-directory: ./cli/ diff --git a/.github/workflows/cli_codeql.yml b/.github/workflows/cli_codeql.yml new file mode 100644 index 000000000..529636c27 --- /dev/null +++ b/.github/workflows/cli_codeql.yml @@ -0,0 +1,44 @@ +name: CLI CodeQL + +on: + push: + paths: + - "cli/**" + - ".github/workflows/cli_codeql.yml" + pull_request: + types: opened + paths: + - "cli/**" + - ".github/workflows/cli_codeql.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.21" + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "go" + queries: security-and-quality + - name: Build + run: | + cd ./cli/ && go build -o cli main.go + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:go" diff --git a/.github/workflows/mobile_codeql.yml b/.github/workflows/mobile_codeql.yml new file mode 100644 index 000000000..3b85ea66a --- /dev/null +++ b/.github/workflows/mobile_codeql.yml @@ -0,0 +1,41 @@ +name: Mobile CodeQL + +on: + push: + paths: + - "frontend/sac-mobile/**" + - ".github/workflows/mobile_codeql.yml" + pull_request: + types: opened + paths: + - "frontend/sac-mobile/**" + - ".github/workflows/mobile_codeql.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "javascript-typescript" + queries: security-and-quality + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + with: + working-directory: frontend/sac-mobile + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:javascript-typescript" diff --git a/.github/workflows/web_codeql.yml b/.github/workflows/web_codeql.yml new file mode 100644 index 000000000..1b6b87bc0 --- /dev/null +++ b/.github/workflows/web_codeql.yml @@ -0,0 +1,41 @@ +name: Web CodeQL + +on: + push: + paths: + - "frontend/sac-web/**" + - ".github/workflows/mweb_codeql.yml" + pull_request: + types: opened + paths: + - "frontend/sac-web/**" + - ".github/workflows/web_codeql.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "javascript-typescript" + queries: security-and-quality + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + with: + working-directory: frontend/sac-web + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:javascript-typescript" diff --git a/.gitignore b/.gitignore index df5f21020..46329301c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .DS_Store .env sac-cli +.vscode +.trunk \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 510bfc357..7b2f62a13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,7 @@ - [Node.js](https://nodejs.org/en/) - [Yarn](https://yarnpkg.com/) - [Go](https://golang.org/) + > Do not install through brew, use the official website - [Docker](https://www.docker.com/) - [PostgreSQL](https://www.postgresql.org/) - Install through brew: `brew install postgresql@15` @@ -22,20 +23,20 @@ 1. **Clone the repository** - ```bash + ```console git clone git@github.com:GenerateNU/sac.git ``` 2. **Install dependencies** - ```bash + ```console cd frontend/* yarn install ``` - If you get an error about `expo-cli` not being installed, run `yarn global add expo-cli` and then run `yarn install` again. - ```bash + ```console cd server go get ./... ``` @@ -46,7 +47,7 @@ 1. **Create client build** - ```bash + ```console cd frotend/sac-mobile eas login eas build:configure @@ -61,7 +62,7 @@ 3. **Start the client** - ```bash + ```console cd frontend/sac-mobile npx expo start --dev-client ``` @@ -74,40 +75,40 @@ - MacOS - ```bash + ```console brew services start postgresql@15 ``` - Windows - ```bash + ```console pg_ctl -D /usr/local/var/postgres start ``` 2. **Create a user** - ```bash + ```console createdb ``` 3. **Create a database** - ```bash + ```console psql // opens psql shell CREATE DATABASE sac; ``` 4. **Create a user** - ```bash + ```console createuser postgres -U ``` # Commands -### React Native +## React Native - ```bash + ```console npx expo start --dev-client // runnning dev client npx expo start --dev-client --ios // specific platform yarn format // format code @@ -115,9 +116,9 @@ yarn test // run tests ``` -### Go +## Go - ```bash + ```console go run main.go // run server go test ./... // run tests go fmt ./... // format code @@ -128,20 +129,38 @@ To install use `./install.sh` and then run `sac-cli` to see all commands. - ```bash - sac-cli migrate // run migrations - sac-cli reset // reset database - sac-cli swagger // generate swagger docs - sac-cli lint // lint code - sac-cli format // format code - sac-cli test // run tests + ```console + NAME: + sac-cli - CLI for SAC + + USAGE: + sac-cli [global options] command [command options] + + COMMANDS: + swagger, swag Updates the swagger documentation + be Run the backend + test, t Runs tests + format, f Runs formatting tools + lint, l Runs linting tools + help, h Shows a list of commands or help for one command + Database Operations: + clean Remove databases used for testing + migrate Migrate the database, creating tables and relationships + insert, i Inserts mock data into the database + reset, r Resets the database, dropping all tables, clearing data, and re-running migrations + * can use --data to just reset data and not drop tables + drop, d Drop data with a migration or drops the entire database + * can use --data to just drop data and not drop tables + + GLOBAL OPTIONS: + --help, -h show help ``` # Git Flow 1. **Create a new branch** - ```bash + ```console git checkout -b // this is determined by your ticket name ``` @@ -149,7 +168,7 @@ - **Commit changes** - ```bash + ```console git add . git commit ``` @@ -160,13 +179,13 @@ 3. **Push changes to GitHub** - ```bash + ```console git push ``` or - ```bash + ```console git push origin ``` diff --git a/README.md b/README.md index 03c587783..fb5635004 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,59 @@
- - - Go Workflow Status + + Backend Go Report + + + Backend Workflow Status + + + + Backend CodeQL Workflow Status + + +
+ + + CLI Go Report + + + CLI Workflow Status + + + + CLI CodeQL Workflow Status + + +
+ + + Mobile CodeQL Workflow Status + + +
+ + + Web CodeQL Workflow Status + +
+ +## Repo Activity + +![Repo Activity](https://repobeats.axiom.co/api/embed/0c57b86b156d377fcc75a6e482bf921acc8c550b.svg "Repobeats Analytics Image") + +## Contributors + +
+ +
diff --git a/backend/.golangci.yml b/backend/.golangci.yml new file mode 100644 index 000000000..0eab32c33 --- /dev/null +++ b/backend/.golangci.yml @@ -0,0 +1,23 @@ +linters: + enable: + - cyclop + - exportloopref + - gocritic + - gosec + - ineffassign + - misspell + - prealloc + - unconvert + - unparam + - goimports + - whitespace + +linters-settings: + whitespace: + multi-func: true + +issues: + exclude-rules: + - path: tests/api/helpers/ + linters: + - cyclop diff --git a/backend/go.mod b/backend/go.mod index 274641a7d..0dadcf9e0 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,15 +1,24 @@ module github.com/GenerateNU/sac/backend -go 1.21.1 +go 1.22.0 require ( - github.com/go-playground/validator/v10 v10.16.0 + github.com/garrettladley/mattress v0.2.2 + github.com/go-playground/validator/v10 v10.17.0 + github.com/goccy/go-json v0.10.2 github.com/gofiber/fiber/v2 v2.52.0 - github.com/gofiber/swagger v0.1.14 + github.com/gofiber/swagger v1.0.0 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.6.0 + github.com/huandu/go-assert v1.1.6 + github.com/mcnijman/go-emailaddress v1.1.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/spf13/viper v1.18.2 - github.com/swaggo/swag v1.16.2 - gorm.io/driver/postgres v1.5.4 - gorm.io/gorm v1.25.5 + github.com/swaggo/swag v1.16.3 + golang.org/x/crypto v0.19.0 + golang.org/x/text v0.14.0 + gorm.io/driver/postgres v1.5.6 + gorm.io/gorm v1.25.7 ) require ( @@ -41,6 +50,8 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/andybalholm/brotli v1.0.5 // indirect + github.com/awnumar/memcall v0.2.0 // indirect + github.com/awnumar/memguard v0.22.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect @@ -50,10 +61,7 @@ require ( github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/goccy/go-json v0.10.2 - github.com/google/uuid v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/go-assert v1.1.6 github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.4.3 // indirect @@ -63,15 +71,12 @@ require ( github.com/klauspost/compress v1.17.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mcnijman/go-emailaddress v1.1.1 - github.com/mitchellh/mapstructure v1.5.0 github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -85,11 +90,9 @@ require ( github.com/valyala/tcplisten v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index b4a679821..3075a166f 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,4 +1,3 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -48,6 +47,10 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZ github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI= +github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= +github.com/awnumar/memguard v0.22.4 h1:1PLgKcgGPeExPHL8dCOWGVjIbQUBgJv9OL0F/yE1PqQ= +github.com/awnumar/memguard v0.22.4/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -59,6 +62,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/garrettladley/mattress v0.2.2 h1:kL/AvDmas6DaAweHwdsm3IosY9zJIGj2uO+byxzhyrU= +github.com/garrettladley/mattress v0.2.2/go.mod h1:OWKIRc9wC3gtD3Ng/nUuNEiR1TJvRYLmn/KZYw9nl5Q= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -75,20 +80,20 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/gofiber/swagger v0.1.14 h1:o524wh4QaS4eKhUCpj7M0Qhn8hvtzcyxDsfZLXuQcRI= -github.com/gofiber/swagger v0.1.14/go.mod h1:DCk1fUPsj+P07CKaZttBbV1WzTZSQcSxfub8y9/BFr8= +github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc= +github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs= @@ -110,8 +115,6 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -127,13 +130,11 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= @@ -145,20 +146,17 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -184,84 +182,43 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= -github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= -github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= -github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -270,17 +227,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= -gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU= +gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/backend/src/auth/password.go b/backend/src/auth/password.go index 109980d02..852d5c2ed 100644 --- a/backend/src/auth/password.go +++ b/backend/src/auth/password.go @@ -56,9 +56,8 @@ var ( ErrIncompatibleVersion = errors.New("incompatible version of argon2") ) -func ComparePasswordAndHash(password, encodedHash string) (bool, error) { +func ComparePasswordAndHash(password string, encodedHash string) (bool, error) { p, salt, hash, err := decodeHash(encodedHash) - if err != nil { return false, err } @@ -82,7 +81,6 @@ func decodeHash(encodedHash string) (p *params, salt []byte, hash []byte, err er var version int _, err = fmt.Sscanf(vals[2], "v=%d", &version) - if err != nil { return nil, nil, nil, err } @@ -94,13 +92,11 @@ func decodeHash(encodedHash string) (p *params, salt []byte, hash []byte, err er p = ¶ms{} _, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism) - if err != nil { return nil, nil, nil, err } salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4]) - if err != nil { return nil, nil, nil, err } @@ -108,7 +104,6 @@ func decodeHash(encodedHash string) (p *params, salt []byte, hash []byte, err er p.saltLength = uint32(len(salt)) hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5]) - if err != nil { return nil, nil, nil, err } diff --git a/backend/src/auth/tokens.go b/backend/src/auth/tokens.go new file mode 100644 index 000000000..3f2c96543 --- /dev/null +++ b/backend/src/auth/tokens.go @@ -0,0 +1,188 @@ +package auth + +import ( + "time" + + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/types" + + m "github.com/garrettladley/mattress" + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt" +) + +func CreateTokenPair(id string, role string, authSettings config.AuthSettings) (*string, *string, *errors.Error) { + accessToken, catErr := CreateAccessToken(id, role, authSettings.AccessTokenExpiry, authSettings.AccessKey) + if catErr != nil { + return nil, nil, catErr + } + + refreshToken, crtErr := CreateRefreshToken(id, authSettings.RefreshTokenExpiry, authSettings.RefreshKey) + if crtErr != nil { + return nil, nil, crtErr + } + + return accessToken, refreshToken, nil +} + +// CreateAccessToken creates a new access token for the user +func CreateAccessToken(id string, role string, accessExpiresAfter uint, accessToken *m.Secret[string]) (*string, *errors.Error) { + if id == "" || role == "" { + return nil, &errors.FailedToCreateAccessToken + } + + accessTokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, &types.CustomClaims{ + StandardClaims: jwt.StandardClaims{ + IssuedAt: time.Now().Unix(), + Issuer: id, + ExpiresAt: time.Now().Add(time.Hour * time.Duration(accessExpiresAfter)).Unix(), + }, + Role: role, + }) + + returnedAccessToken, err := SignToken(accessTokenClaims, accessToken) + if err != nil { + return nil, err + } + + return returnedAccessToken, nil +} + +// CreateRefreshToken creates a new refresh token for the user +func CreateRefreshToken(id string, refreshExpiresAfter uint, refreshKey *m.Secret[string]) (*string, *errors.Error) { + if id == "" { + return nil, &errors.FailedToCreateRefreshToken + } + + refreshTokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{ + IssuedAt: time.Now().Unix(), + Issuer: id, + ExpiresAt: time.Now().Add(time.Hour * 24 * time.Duration(refreshExpiresAfter)).Unix(), + }) + + returnedRefreshToken, err := SignToken(refreshTokenClaims, refreshKey) + if err != nil { + return nil, err + } + + return returnedRefreshToken, nil +} + +func SignToken(token *jwt.Token, key *m.Secret[string]) (*string, *errors.Error) { + if token == nil || key.Expose() == "" { + return nil, &errors.FailedToSignToken + } + + tokenString, err := token.SignedString([]byte(key.Expose())) + if err != nil { + return nil, &errors.FailedToSignToken + } + return &tokenString, nil +} + +// CreateCookie creates a new cookie +func CreateCookie(name string, value string, expires time.Time) *fiber.Cookie { + return &fiber.Cookie{ + Name: name, + Value: value, + Expires: expires, + HTTPOnly: true, + } +} + +// ExpireCookie expires a cookie +func ExpireCookie(name string) *fiber.Cookie { + return &fiber.Cookie{ + Name: name, + Value: "", + Expires: time.Now().Add(-time.Hour), + HTTPOnly: true, + } +} + +// RefreshAccessToken refreshes the access token +func RefreshAccessToken(refreshCookie string, role string, refreshKey *m.Secret[string], accessExpiresAfter uint, accessKey *m.Secret[string]) (*string, *errors.Error) { + // Parse the refresh token + refreshToken, err := ParseRefreshToken(refreshCookie, refreshKey) + if err != nil { + return nil, &errors.FailedToParseRefreshToken + } + + // Extract the claims from the refresh token + claims, ok := refreshToken.Claims.(*jwt.StandardClaims) + if !ok || !refreshToken.Valid { + return nil, &errors.FailedToValidateRefreshToken + } + + // Create a new access token + accessToken, catErr := CreateAccessToken(claims.Issuer, role, accessExpiresAfter, accessKey) + if catErr != nil { + return nil, &errors.FailedToCreateAccessToken + } + + return accessToken, nil +} + +// ParseAccessToken parses the access token +func ParseAccessToken(cookie string, accessKey *m.Secret[string]) (*jwt.Token, error) { + return jwt.ParseWithClaims(cookie, &types.CustomClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(accessKey.Expose()), nil + }) +} + +// ParseRefreshToken parses the refresh token +func ParseRefreshToken(cookie string, refreshKey *m.Secret[string]) (*jwt.Token, error) { + return jwt.ParseWithClaims(cookie, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(refreshKey.Expose()), nil + }) +} + +// GetRoleFromToken gets the role from the custom claims +func GetRoleFromToken(tokenString string, accessKey *m.Secret[string]) (*string, error) { + token, err := ParseAccessToken(tokenString, accessKey) + if err != nil { + return nil, err + } + + claims, ok := token.Claims.(*types.CustomClaims) + if !ok || !token.Valid { + return nil, &errors.FailedToValidateAccessToken + } + + return &claims.Role, nil +} + +// ExtractClaims extracts the claims from the token +func ExtractAccessClaims(tokenString string, accessKey *m.Secret[string]) (*types.CustomClaims, *errors.Error) { + token, err := ParseAccessToken(tokenString, accessKey) + if err != nil { + return nil, &errors.FailedToParseAccessToken + } + + claims, ok := token.Claims.(*types.CustomClaims) + if !ok || !token.Valid { + return nil, &errors.FailedToValidateAccessToken + } + + return claims, nil +} + +// ExtractClaims extracts the claims from the token +func ExtractRefreshClaims(tokenString string, refreshKey *m.Secret[string]) (*jwt.StandardClaims, *errors.Error) { + token, err := ParseRefreshToken(tokenString, refreshKey) + if err != nil { + return nil, &errors.FailedToParseRefreshToken + } + + claims, ok := token.Claims.(*jwt.StandardClaims) + if !ok || !token.Valid { + return nil, &errors.FailedToValidateRefreshToken + } + + return claims, nil +} + +func IsBlacklisted(token string) bool { + return false +} diff --git a/backend/src/config/application.go b/backend/src/config/application.go new file mode 100644 index 000000000..dd2591408 --- /dev/null +++ b/backend/src/config/application.go @@ -0,0 +1,7 @@ +package config + +type ApplicationSettings struct { + Port uint16 `yaml:"port"` + Host string `yaml:"host"` + BaseUrl string `yaml:"baseurl"` +} diff --git a/backend/src/config/auth.go b/backend/src/config/auth.go new file mode 100644 index 000000000..babc0cc2e --- /dev/null +++ b/backend/src/config/auth.go @@ -0,0 +1,40 @@ +package config + +import ( + "errors" + + m "github.com/garrettladley/mattress" +) + +type AuthSettings struct { + AccessKey *m.Secret[string] + RefreshKey *m.Secret[string] + AccessTokenExpiry uint + RefreshTokenExpiry uint +} + +type intermediateAuthSettings struct { + AccessKey string `yaml:"accesskey"` + RefreshKey string `yaml:"refreshkey"` + AccessTokenExpiry uint `yaml:"accesstokenexpiry"` + RefreshTokenExpiry uint `yaml:"refreshtokenexpiry"` +} + +func (int *intermediateAuthSettings) into() (*AuthSettings, error) { + accessToken, err := m.NewSecret(int.AccessKey) + if err != nil { + return nil, errors.New("failed to create secret from access key") + } + + refreshToken, err := m.NewSecret(int.RefreshKey) + if err != nil { + return nil, errors.New("failed to create secret from refresh key") + } + + return &AuthSettings{ + AccessKey: accessToken, + RefreshKey: refreshToken, + AccessTokenExpiry: int.AccessTokenExpiry, + RefreshTokenExpiry: int.RefreshTokenExpiry, + }, nil +} diff --git a/backend/src/config/aws.go b/backend/src/config/aws.go new file mode 100644 index 000000000..d13045b1c --- /dev/null +++ b/backend/src/config/aws.go @@ -0,0 +1,22 @@ +package config + +import ( + "os" + "github.com/joho/godotenv" +) + +type AWSSettings struct { + BUCKET_NAME string + ID string + SECRET string +} + +func ConfigAWS() AWSSettings { + if err := godotenv.Load(); err != nil { + panic(err) + } + return AWSSettings{ + BUCKET_NAME: os.Getenv("BUCKET_NAME"), + ID: os.Getenv("AWS_ID"), + SECRET: os.Getenv("AWS_SECRET")} +} \ No newline at end of file diff --git a/backend/src/config/config.go b/backend/src/config/config.go index 0fc4eed0c..aa5b9176a 100644 --- a/backend/src/config/config.go +++ b/backend/src/config/config.go @@ -1,9 +1,7 @@ package config import ( - "fmt" "os" - "strconv" "github.com/joho/godotenv" "github.com/spf13/viper" @@ -13,62 +11,40 @@ type Settings struct { Application ApplicationSettings `yaml:"application"` Database DatabaseSettings `yaml:"database"` SuperUser SuperUserSettings `yaml:"superuser"` + Auth AuthSettings AWS AWSSettings } -type ProductionSettings struct { - Database ProductionDatabaseSettings `yaml:"database"` - Application ProductionApplicationSettings `yaml:"application"` -} - -type ApplicationSettings struct { - Port uint16 `yaml:"port"` - Host string `yaml:"host"` - BaseUrl string `yaml:"baseurl"` -} - -type ProductionApplicationSettings struct { - Port uint16 `yaml:"port"` - Host string `yaml:"host"` -} - -type DatabaseSettings struct { - Username string `yaml:"username"` - Password string `yaml:"password"` - Port uint `yaml:"port"` - Host string `yaml:"host"` - DatabaseName string `yaml:"databasename"` - RequireSSL bool `yaml:"requiressl"` -} - -type ProductionDatabaseSettings struct { - RequireSSL bool `yaml:"requiressl"` +type intermediateSettings struct { + Application ApplicationSettings `yaml:"application"` + Database intermediateDatabaseSettings `yaml:"database"` + SuperUser intermediateSuperUserSettings `yaml:"superuser"` + Auth intermediateAuthSettings `yaml:"authsecret"` + AWS AWSSettings } -func (s *DatabaseSettings) WithoutDb() string { - var sslMode string - if s.RequireSSL { - sslMode = "require" - } else { - sslMode = "disable" +func (int *intermediateSettings) into() (*Settings, error) { + databaseSettings, err := int.Database.into() + if err != nil { + return nil, err } - return fmt.Sprintf("host=%s port=%d user=%s password=%s sslmode=%s", - s.Host, s.Port, s.Username, s.Password, sslMode) -} - -func (s *DatabaseSettings) WithDb() string { - return fmt.Sprintf("%s dbname=%s", s.WithoutDb(), s.DatabaseName) -} + superUserSettings, err := int.SuperUser.into() + if err != nil { + return nil, err + } -type SuperUserSettings struct { - Password string `yaml:"password"` -} + authSettings, err := int.Auth.into() + if err != nil { + return nil, err + } -type AWSSettings struct { - BUCKET_NAME string - ID string - SECRET string + return &Settings{ + Application: int.Application, + Database: *databaseSettings, + SuperUser: *superUserSettings, + Auth: *authSettings, + }, nil } func configAWS() AWSSettings { @@ -88,8 +64,7 @@ const ( EnvironmentProduction Environment = "production" ) -func GetConfiguration(path string) (Settings, error) { - +func GetConfiguration(path string) (*Settings, error) { var environment Environment if env := os.Getenv("APP_ENVIRONMENT"); env != "" { environment = Environment(env) @@ -100,64 +75,9 @@ func GetConfiguration(path string) (Settings, error) { v := viper.New() v.SetConfigType("yaml") v.AddConfigPath(path) - AWSSettings := configAWS() if environment == EnvironmentLocal { - var settings Settings - - v.SetConfigName(string(environment)) - - if err := v.ReadInConfig(); err != nil { - return settings, fmt.Errorf("failed to read %s configuration: %w", string(environment), err) - } - - if err := v.Unmarshal(&settings); err != nil { - return settings, fmt.Errorf("failed to unmarshal configuration: %w", err) - } - settings.AWS = AWSSettings - return settings, nil + return readLocal(v) } else { - var prodSettings ProductionSettings - - v.SetConfigName(string(environment)) - - if err := v.ReadInConfig(); err != nil { - return Settings{}, fmt.Errorf("failed to read %s configuration: %w", string(environment), err) - } - - if err := v.Unmarshal(&prodSettings); err != nil { - return Settings{}, fmt.Errorf("failed to unmarshal configuration: %w", err) - } - - appPrefix := "APP_" - applicationPrefix := fmt.Sprintf("%sAPPLICATION__", appPrefix) - dbPrefix := fmt.Sprintf("%sDATABASE__", appPrefix) - superUserPrefix := fmt.Sprintf("%sSUPERUSER__", appPrefix) - - portStr := os.Getenv(fmt.Sprintf("%sPORT", appPrefix)) - portInt, err := strconv.ParseUint(portStr, 10, 16) - - if err != nil { - return Settings{}, fmt.Errorf("failed to parse port: %w", err) - } - - return Settings{ - Application: ApplicationSettings{ - Port: uint16(portInt), - Host: prodSettings.Application.Host, - BaseUrl: os.Getenv(fmt.Sprintf("%sBASE_URL", applicationPrefix)), - }, - Database: DatabaseSettings{ - Username: os.Getenv(fmt.Sprintf("%sUSERNAME", dbPrefix)), - Password: os.Getenv(fmt.Sprintf("%sPASSWORD", dbPrefix)), - Host: os.Getenv(fmt.Sprintf("%sHOST", dbPrefix)), - Port: uint(portInt), - DatabaseName: os.Getenv(fmt.Sprintf("%sDATABASE_NAME", dbPrefix)), - RequireSSL: prodSettings.Database.RequireSSL, - }, - SuperUser: SuperUserSettings{ - Password: os.Getenv(fmt.Sprintf("%sPASSWORD", superUserPrefix)), - }, - AWS: AWSSettings, - }, nil + return readProd(v) } } diff --git a/backend/src/config/database.go b/backend/src/config/database.go new file mode 100644 index 000000000..422d17b69 --- /dev/null +++ b/backend/src/config/database.go @@ -0,0 +1,58 @@ +package config + +import ( + "errors" + "fmt" + + m "github.com/garrettladley/mattress" +) + +type DatabaseSettings struct { + Username string + Password *m.Secret[string] + Port uint + Host string + DatabaseName string + RequireSSL bool +} + +func (int *intermediateDatabaseSettings) into() (*DatabaseSettings, error) { + password, err := m.NewSecret(int.Password) + if err != nil { + return nil, errors.New("failed to create secret from password") + } + + return &DatabaseSettings{ + Username: int.Username, + Password: password, + Port: int.Port, + Host: int.Host, + DatabaseName: int.DatabaseName, + RequireSSL: int.RequireSSL, + }, nil +} + +type intermediateDatabaseSettings struct { + Username string `yaml:"username"` + Password string `yaml:"password"` + Port uint `yaml:"port"` + Host string `yaml:"host"` + DatabaseName string `yaml:"databasename"` + RequireSSL bool `yaml:"requiressl"` +} + +func (s *DatabaseSettings) WithoutDb() string { + var sslMode string + if s.RequireSSL { + sslMode = "require" + } else { + sslMode = "disable" + } + + return fmt.Sprintf("host=%s port=%d user=%s password=%s sslmode=%s", + s.Host, s.Port, s.Username, s.Password.Expose(), sslMode) +} + +func (s *DatabaseSettings) WithDb() string { + return fmt.Sprintf("%s dbname=%s", s.WithoutDb(), s.DatabaseName) +} diff --git a/backend/src/config/local.go b/backend/src/config/local.go new file mode 100644 index 000000000..aae7f8859 --- /dev/null +++ b/backend/src/config/local.go @@ -0,0 +1,32 @@ +package config + +import ( + "fmt" + "github.com/spf13/viper" +) + +func readLocal(v *viper.Viper) (*Settings, error) { + var intermediateSettings intermediateSettings + + env := string(EnvironmentLocal) + + v.SetConfigName(env) + + if err := v.ReadInConfig(); err != nil { + return nil, fmt.Errorf("failed to read %s configuration: %w", env, err) + } + + if err := v.Unmarshal(&intermediateSettings); err != nil { + return nil, fmt.Errorf("failed to unmarshal configuration: %w", err) + } + + settings, err := intermediateSettings.into() + if err != nil { + return nil, fmt.Errorf("failed to convert intermediate settings into final settings: %w", err) + } + + AWSSettings := ConfigAWS() + intermediateSettings.AWS = AWSSettings + + return settings, nil +} diff --git a/backend/src/config/production.go b/backend/src/config/production.go new file mode 100644 index 000000000..f53c8810d --- /dev/null +++ b/backend/src/config/production.go @@ -0,0 +1,114 @@ +package config + +import ( + "errors" + "fmt" + "os" + "strconv" + + m "github.com/garrettladley/mattress" + "github.com/spf13/viper" +) + +type ProductionSettings struct { + Database ProductionDatabaseSettings `yaml:"database"` + Application ProductionApplicationSettings `yaml:"application"` +} + +type ProductionDatabaseSettings struct { + RequireSSL bool `yaml:"requiressl"` +} + +type ProductionApplicationSettings struct { + Port uint16 `yaml:"port"` + Host string `yaml:"host"` +} + +func readProd(v *viper.Viper) (*Settings, error) { + var prodSettings ProductionSettings + + env := string(EnvironmentProduction) + + v.SetConfigName(env) + + if err := v.ReadInConfig(); err != nil { + return nil, fmt.Errorf("failed to read %s configuration: %w", env, err) + } + + if err := v.Unmarshal(&prodSettings); err != nil { + return nil, fmt.Errorf("failed to unmarshal configuration: %w", err) + } + + appPrefix := "APP_" + applicationPrefix := fmt.Sprintf("%sAPPLICATION__", appPrefix) + dbPrefix := fmt.Sprintf("%sDATABASE__", appPrefix) + superUserPrefix := fmt.Sprintf("%sSUPERUSER__", appPrefix) + authSecretPrefix := fmt.Sprintf("%sAUTHSECRET__", appPrefix) + + authAccessExpiry := os.Getenv(fmt.Sprintf("%sACCESS_TOKEN_EXPIRY", authSecretPrefix)) + authRefreshExpiry := os.Getenv(fmt.Sprintf("%sREFRESH_TOKEN_EXPIRY", authSecretPrefix)) + + authAccessExpiryInt, err := strconv.ParseUint(authAccessExpiry, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse access token expiry: %w", err) + } + + authRefreshExpiryInt, err := strconv.ParseUint(authRefreshExpiry, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse refresh token expiry: %w", err) + } + + portStr := os.Getenv(fmt.Sprintf("%sPORT", appPrefix)) + portInt, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, fmt.Errorf("failed to parse port: %w", err) + } + + dbPassword, err := m.NewSecret(os.Getenv(fmt.Sprintf("%sUSERNAME", dbPrefix))) + if err != nil { + return nil, errors.New("failed to create secret from database password") + } + + superPassword, err := m.NewSecret(os.Getenv(fmt.Sprintf("%sPASSWORD", superUserPrefix))) + if err != nil { + return nil, errors.New("failed to create secret from super user password") + } + + authAccessKey, err := m.NewSecret(os.Getenv(fmt.Sprintf("%sACCESS_TOKEN", authSecretPrefix))) + if err != nil { + return nil, errors.New("failed to create secret from access token") + } + + authRefreshKey, err := m.NewSecret(os.Getenv(fmt.Sprintf("%sREFRESH_TOKEN", authSecretPrefix))) + if err != nil { + return nil, errors.New("failed to create secret from refresh token") + } + + AWSSettings := ConfigAWS() + + return &Settings{ + Application: ApplicationSettings{ + Port: uint16(portInt), + Host: prodSettings.Application.Host, + BaseUrl: os.Getenv(fmt.Sprintf("%sBASE_URL", applicationPrefix)), + }, + Database: DatabaseSettings{ + Username: os.Getenv(fmt.Sprintf("%sUSERNAME", dbPrefix)), + Password: dbPassword, + Host: os.Getenv(fmt.Sprintf("%sHOST", dbPrefix)), + Port: uint(portInt), + DatabaseName: os.Getenv(fmt.Sprintf("%sDATABASE_NAME", dbPrefix)), + RequireSSL: prodSettings.Database.RequireSSL, + }, + SuperUser: SuperUserSettings{ + Password: superPassword, + }, + Auth: AuthSettings{ + AccessKey: authAccessKey, + RefreshKey: authRefreshKey, + AccessTokenExpiry: uint(authAccessExpiryInt), + RefreshTokenExpiry: uint(authRefreshExpiryInt), + }, + AWS: AWSSettings, + }, nil +} diff --git a/backend/src/config/super_user.go b/backend/src/config/super_user.go new file mode 100644 index 000000000..2a75c88ef --- /dev/null +++ b/backend/src/config/super_user.go @@ -0,0 +1,26 @@ +package config + +import ( + "errors" + + m "github.com/garrettladley/mattress" +) + +type SuperUserSettings struct { + Password *m.Secret[string] +} + +type intermediateSuperUserSettings struct { + Password string `yaml:"password"` +} + +func (int *intermediateSuperUserSettings) into() (*SuperUserSettings, error) { + password, err := m.NewSecret(int.Password) + if err != nil { + return nil, errors.New("failed to create secret from password") + } + + return &SuperUserSettings{ + Password: password, + }, nil +} diff --git a/backend/src/controllers/auth.go b/backend/src/controllers/auth.go new file mode 100644 index 000000000..8ab0b7bd5 --- /dev/null +++ b/backend/src/controllers/auth.go @@ -0,0 +1,148 @@ +package controllers + +import ( + "time" + + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/gofiber/fiber/v2" +) + +type AuthController struct { + authService services.AuthServiceInterface + blacklist []string + AuthSettings config.AuthSettings +} + +func NewAuthController(authService services.AuthServiceInterface, authSettings config.AuthSettings) *AuthController { + return &AuthController{authService: authService, blacklist: []string{}, AuthSettings: authSettings} +} + +// Me godoc +// +// @Summary Gets the current user +// @Description Returns the current user +// @ID get-current-user +// @Tags user +// @Produce json +// @Success 200 {object} models.User +// @Failure 401 {string} string "failed to get current user" +// @Router /api/v1/auth/me [get] +func (a *AuthController) Me(c *fiber.Ctx) error { + claims, err := types.From(c) + if err != nil { + return err.FiberError(c) + } + user, err := a.authService.Me(claims.Issuer) + if err != nil { + return err.FiberError(c) + } + + return c.JSON(user) +} + +// Login godoc +// +// @Summary Logs in a user +// @Description Logs in a user +// @ID login-user +// @Tags user +// @Accept json +// @Produce json +// @Param userBody body []string true "User Body" +// @Success 200 {object} string "success" +// @Failure 400 {string} string "failed to parse body" +// @Failure 401 {string} string "failed to login user" +// @Router /api/v1/auth/login [post] +func (a *AuthController) Login(c *fiber.Ctx) error { + var userBody models.LoginUserResponseBody + + if err := c.BodyParser(&userBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + user, err := a.authService.Login(userBody) + if err != nil { + return err.FiberError(c) + } + + accessToken, refreshToken, err := auth.CreateTokenPair(user.ID.String(), string(user.Role), a.AuthSettings) + if err != nil { + return err.FiberError(c) + } + + // Set the tokens in the response + c.Cookie(auth.CreateCookie("access_token", *accessToken, time.Now().Add(time.Minute*time.Duration(a.AuthSettings.AccessTokenExpiry)))) + c.Cookie(auth.CreateCookie("refresh_token", *refreshToken, time.Now().Add(time.Hour*time.Duration(a.AuthSettings.RefreshTokenExpiry)))) + + return utilities.FiberMessage(c, fiber.StatusOK, "success") +} + +// Refresh godoc +// +// @Summary Refreshes a user's access token +// @Description Refreshes a user's access token +// @ID refresh-user +// @Tags user +// @Accept json +// @Produce json +// @Success 200 {object} string "success" +// @Failure 401 {string} string "failed to refresh access token" +// @Router /api/v1/auth/refresh [get] +func (a *AuthController) Refresh(c *fiber.Ctx) error { + // Extract token values from cookies + refreshTokenValue := c.Cookies("refresh_token") + + // Extract id from refresh token + claims, err := auth.ExtractRefreshClaims(refreshTokenValue, a.AuthSettings.RefreshKey) + if err != nil { + return err.FiberError(c) + } + + role, err := a.authService.GetRole(claims.Issuer) + if err != nil { + return err.FiberError(c) + } + + accessToken, err := auth.RefreshAccessToken(refreshTokenValue, string(*role), a.AuthSettings.RefreshKey, a.AuthSettings.AccessTokenExpiry, a.AuthSettings.AccessKey) + if err != nil { + return err.FiberError(c) + } + + // Set the access token in the response (e.g., in a cookie or JSON response) + c.Cookie(auth.CreateCookie("access_token", *accessToken, time.Now().Add(time.Minute*60))) + + return utilities.FiberMessage(c, fiber.StatusOK, "success") +} + +// Logout godoc +// +// @Summary Logs out a user +// @Description Logs out a user +// @ID logout-user +// @Tags user +// @Accept json +// @Produce json +// @Success 200 {object} string +// @Failure 401 {string} string "failed to logout user" +// @Router /api/v1/auth/logout [get] +func (a *AuthController) Logout(c *fiber.Ctx) error { + // Extract token values from cookies + accessTokenValue := c.Cookies("access_token") + refreshTokenValue := c.Cookies("refresh_token") + + // TODO: Redis + a.blacklist = append(a.blacklist, accessTokenValue) + a.blacklist = append(a.blacklist, refreshTokenValue) + + // Expire and clear the cookies + c.Cookie(auth.ExpireCookie("access_token")) + c.Cookie(auth.ExpireCookie("refresh_token")) + + return utilities.FiberMessage(c, fiber.StatusOK, "success") +} diff --git a/backend/src/controllers/category.go b/backend/src/controllers/category.go index ce4732fe7..6d4655dfe 100644 --- a/backend/src/controllers/category.go +++ b/backend/src/controllers/category.go @@ -1,6 +1,8 @@ package controllers import ( + "strconv" + "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/services" @@ -32,14 +34,103 @@ func (t *CategoryController) CreateCategory(c *fiber.Ctx) error { var categoryBody models.CategoryRequestBody if err := c.BodyParser(&categoryBody); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } newCategory, err := t.categoryService.CreateCategory(categoryBody) - if err != nil { return err.FiberError(c) } return c.Status(fiber.StatusCreated).JSON(newCategory) } + +// GetCategories godoc +// +// @Summary Retrieve all categories +// @Description Retrieves all existing categories +// @ID get-categories +// @Tags category +// @Produce json +// @Success 200 {object} []models.Category +// @Failure 500 {string} string "unable to retrieve categories" +// @Router /api/v1/category/ [get] +func (t *CategoryController) GetCategories(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + categories, err := t.categoryService.GetCategories(c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&categories) +} + +// GetCategory godoc +// +// @Summary Retrieve a category +// @Description Retrieve a category by its ID +// @ID get-category +// @Tags category +// @Produce json +// @Success 200 {object} models.Category +// @Failure 400 {string} string "failed to validate id" +// @Failure 404 {string} string "faied to find category" +// @Failure 500 {string} string "failed to retrieve category" +// @Router /api/v1/category/{id} [get] +func (t *CategoryController) GetCategory(c *fiber.Ctx) error { + category, err := t.categoryService.GetCategory(c.Params("categoryID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&category) +} + +// DeleteCategory godoc +// +// @Summary Delete a category +// @Description Delete a category by ID +// @ID delete-category +// @Tags category +// @Produce json +// @Success 204 {object} +// @Failure 400 {string} string "failed to validate id" +// @Failure 404 {string} string "failed to find category" +// @Failure 500 {string} string "failed to delete category" +// @Router /api/v1/category/{id} [delete] +func (t *CategoryController) DeleteCategory(c *fiber.Ctx) error { + if err := t.categoryService.DeleteCategory(c.Params("categoryID")); err != nil { + return err.FiberError(c) + } + + return c.SendStatus(fiber.StatusNoContent) +} + +// UpdateCategory godoc +// +// @Summary Updates a category +// @Description Updates a category +// @ID update-category +// @Tags category +// @Produce json +// @Success 200 {object} models.Category +// @Failure 400 {string} string "failed to validate id" +// @Failure 404 {string} string "failed to find category" +// @Failure 500 {string} string "failed to update category" +// @Router /api/v1/category/{id} [patch] +func (t *CategoryController) UpdateCategory(c *fiber.Ctx) error { + var category models.CategoryRequestBody + + if err := c.BodyParser(&category); err != nil { + return errors.FailedToValidateCategory.FiberError(c) + } + + updatedCategory, err := t.categoryService.UpdateCategory(c.Params("categoryID"), category) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(updatedCategory) +} diff --git a/backend/src/controllers/category_tag.go b/backend/src/controllers/category_tag.go new file mode 100644 index 000000000..0c3fa7a27 --- /dev/null +++ b/backend/src/controllers/category_tag.go @@ -0,0 +1,38 @@ +package controllers + +import ( + "strconv" + + "github.com/GenerateNU/sac/backend/src/services" + + "github.com/gofiber/fiber/v2" +) + +type CategoryTagController struct { + categoryTagService services.CategoryTagServiceInterface +} + +func NewCategoryTagController(categoryTagService services.CategoryTagServiceInterface) *CategoryTagController { + return &CategoryTagController{categoryTagService: categoryTagService} +} + +func (t *CategoryTagController) GetTagsByCategory(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + tags, err := t.categoryTagService.GetTagsByCategory(c.Params("categoryID"), c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&tags) +} + +func (t *CategoryTagController) GetTagByCategory(c *fiber.Ctx) error { + tag, err := t.categoryTagService.GetTagByCategory(c.Params("categoryID"), c.Params("tagID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&tag) +} diff --git a/backend/src/controllers/club.go b/backend/src/controllers/club.go index d2bbe8340..2b554feeb 100644 --- a/backend/src/controllers/club.go +++ b/backend/src/controllers/club.go @@ -1,13 +1,14 @@ package controllers import ( + "strconv" + "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/services" "github.com/gofiber/fiber/v2" ) -// Point of Contact type ClubController struct { clubService services.ClubServiceInterface } @@ -16,10 +17,70 @@ func NewClubController(clubService services.ClubServiceInterface) *ClubControlle return &ClubController{clubService: clubService} } +func (cl *ClubController) GetAllClubs(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + clubs, err := cl.clubService.GetClubs(c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(clubs) +} + +func (cl *ClubController) CreateClub(c *fiber.Ctx) error { + var clubBody models.CreateClubRequestBody + if err := c.BodyParser(&clubBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + club, err := cl.clubService.CreateClub(clubBody) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusCreated).JSON(club) +} + +func (cl *ClubController) GetClub(c *fiber.Ctx) error { + club, err := cl.clubService.GetClub(c.Params("clubID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(club) +} + +func (cl *ClubController) UpdateClub(c *fiber.Ctx) error { + var clubBody models.UpdateClubRequestBody + + if err := c.BodyParser(&clubBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + updatedClub, err := cl.clubService.UpdateClub(c.Params("clubID"), clubBody) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(updatedClub) +} + +func (cl *ClubController) DeleteClub(c *fiber.Ctx) error { + err := cl.clubService.DeleteClub(c.Params("clubID")) + if err != nil { + return err.FiberError(c) + } + return c.SendStatus(fiber.StatusNoContent) +} + +// Point of Contact + // UpsertPointofContact godoc // -// @Summary Creates or Updates a User -// @Description Creates or Updates a User +// @Summary Creates or Updates a PointofContact +// @Description Creates or Updates a PointofContact // @ID upsert-point-of-contact // @Tags club // @Accept json @@ -34,16 +95,15 @@ func NewClubController(clubService services.ClubServiceInterface) *ClubControlle func (u *ClubController) UpsertPointOfContact(c *fiber.Ctx) error { var pointOfContactBody models.CreatePointOfContactBody if err := c.BodyParser(&pointOfContactBody); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } - pointOfContact, err := u.clubService.UpsertPointOfContact(c.Params("id"), pointOfContactBody) + pointOfContact, err := u.clubService.UpsertPointOfContact(c.Params("clubID"), pointOfContactBody) if err != nil { return err.FiberError(c) } return c.Status(fiber.StatusOK).JSON(pointOfContact) } - // GetAllPointOfContact godoc // // @Summary Gets all point of contact @@ -59,7 +119,7 @@ func (u *ClubController) UpsertPointOfContact(c *fiber.Ctx) error { // @Router api/v1/clubs/:id/poc/:pocId [get] func (u *ClubController) GetAllPointOfContact(c *fiber.Ctx) error { - clubId := c.Params("id") + clubId := c.Params("clubID") pointOfContact, err := u.clubService.GetAllPointOfContacts(clubId) if err != nil { return err.FiberError(c) @@ -67,7 +127,6 @@ func (u *ClubController) GetAllPointOfContact(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(pointOfContact) } - // GetPointOfContact godoc // // @Summary Gets a user @@ -82,8 +141,8 @@ func (u *ClubController) GetAllPointOfContact(c *fiber.Ctx) error { // @Router api/v1/clubs/:id/poc/:pocId [get] func (u *ClubController) GetPointOfContact(c *fiber.Ctx) error { - clubId := c.Params("id") - pocId := c.Params("pocId") + clubId := c.Params("clubID") + pocId := c.Params("pocID") pointOfContact, err := u.clubService.GetPointOfContact(pocId, clubId) if err != nil { return err.FiberError(c) @@ -91,7 +150,6 @@ func (u *ClubController) GetPointOfContact(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(pointOfContact) } - // DeletePointOfContact godoc // // @Summary Deletes the given clubID and pocId @@ -104,8 +162,8 @@ func (u *ClubController) GetPointOfContact(c *fiber.Ctx) error { // @Router api/v1/clubs/:id/poc/:pocId [delete] func (u *ClubController) DeletePointOfContact(c *fiber.Ctx) error { - clubId := c.Params("id") - pocId := c.Params("pocId") + clubId := c.Params("clubID") + pocId := c.Params("pocID") err := u.clubService.DeletePointOfContact(pocId, clubId) if err != nil { return err.FiberError(c) diff --git a/backend/src/controllers/club_contact.go b/backend/src/controllers/club_contact.go new file mode 100644 index 000000000..f23ddf2e0 --- /dev/null +++ b/backend/src/controllers/club_contact.go @@ -0,0 +1,40 @@ +package controllers + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +type ClubContactController struct { + clubContactService services.ClubContactServiceInterface +} + +func NewClubContactController(clubContactService services.ClubContactServiceInterface) *ClubContactController { + return &ClubContactController{clubContactService: clubContactService} +} + +func (cc *ClubContactController) GetClubContacts(c *fiber.Ctx) error { + contacts, err := cc.clubContactService.GetClubContacts(c.Params("clubID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(contacts) +} + +func (cc *ClubContactController) PutContact(c *fiber.Ctx) error { + var contactBody models.PutContactRequestBody + + if err := c.BodyParser(&contactBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + contact, err := cc.clubContactService.PutClubContact(c.Params("clubID"), contactBody) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(contact) +} diff --git a/backend/src/controllers/contact.go b/backend/src/controllers/contact.go new file mode 100644 index 000000000..4ce650882 --- /dev/null +++ b/backend/src/controllers/contact.go @@ -0,0 +1,46 @@ +package controllers + +import ( + "strconv" + + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +type ContactController struct { + contactService services.ContactServiceInterface +} + +func NewContactController(contactService services.ContactServiceInterface) *ContactController { + return &ContactController{contactService: contactService} +} + +func (co *ContactController) GetContact(c *fiber.Ctx) error { + contact, err := co.contactService.GetContact(c.Params("contactID")) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(contact) +} + +func (co *ContactController) GetContacts(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + contacts, err := co.contactService.GetContacts(c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(contacts) +} + +func (co *ContactController) DeleteContact(c *fiber.Ctx) error { + err := co.contactService.DeleteContact(c.Params("contactID")) + if err != nil { + return err.FiberError(c) + } + + return c.SendStatus(fiber.StatusNoContent) +} diff --git a/backend/src/controllers/file.go b/backend/src/controllers/file.go index 3e3706c05..112476134 100644 --- a/backend/src/controllers/file.go +++ b/backend/src/controllers/file.go @@ -23,18 +23,18 @@ func (f *FileController) CreateFile(c *fiber.Ctx) error { var file models.File formFile, err := c.FormFile("img") if err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToProcessRequest}.FiberError(c) + return errors.FailedToProcessRequest.FiberError(c) } fileData, err := formFile.Open() if err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToOpenFile}.FiberError(c) + return errors.FailedToOpenFile.FiberError(c) } buff := make([]byte, 512) if _, err = fileData.Read(buff); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.InvalidImageFormat}.FiberError(c) + return errors.InvalidImageFormat.FiberError(c) } if !((http.DetectContentType(buff) == "image/png") || (http.DetectContentType(buff) == "image/jpeg")) { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidatedData}.FiberError(c) + return errors.FailedToValidatedData.FiberError(c) } defer fileData.Close() fileCreated, errFile := f.fileService.CreateFile(file, formFile, fileData) diff --git a/backend/src/controllers/tag.go b/backend/src/controllers/tag.go index 8da79bb9a..9dccea98c 100644 --- a/backend/src/controllers/tag.go +++ b/backend/src/controllers/tag.go @@ -33,7 +33,7 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { var tagBody models.TagRequestBody if err := c.BodyParser(&tagBody); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } dbTag, err := t.tagService.CreateTag(tagBody) @@ -58,8 +58,7 @@ func (t *TagController) CreateTag(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to retrieve tag" // @Router /api/v1/tags/{id} [get] func (t *TagController) GetTag(c *fiber.Ctx) error { - tag, err := t.tagService.GetTag(c.Params("id")) - + tag, err := t.tagService.GetTag(c.Params("tagID")) if err != nil { return err.FiberError(c) } @@ -87,10 +86,10 @@ func (t *TagController) UpdateTag(c *fiber.Ctx) error { var tagBody models.TagRequestBody if err := c.BodyParser(&tagBody); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } - tag, err := t.tagService.UpdateTag(c.Params("id"), tagBody) + tag, err := t.tagService.UpdateTag(c.Params("tagID"), tagBody) if err != nil { return err.FiberError(c) } @@ -111,7 +110,7 @@ func (t *TagController) UpdateTag(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to delete tag" // @Router /api/v1/tags/{id} [delete] func (t *TagController) DeleteTag(c *fiber.Ctx) error { - err := t.tagService.DeleteTag(c.Params("id")) + err := t.tagService.DeleteTag(c.Params("tagID")) if err != nil { return err.FiberError(c) } diff --git a/backend/src/controllers/user.go b/backend/src/controllers/user.go index a0d82c733..60746d3dd 100644 --- a/backend/src/controllers/user.go +++ b/backend/src/controllers/user.go @@ -1,6 +1,8 @@ package controllers import ( + "strconv" + "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/services" @@ -16,25 +18,6 @@ func NewUserController(userService services.UserServiceInterface) *UserControlle return &UserController{userService: userService} } -// GetAllUsers godoc -// -// @Summary Gets all users -// @Description Returns all users -// @ID get-all-users -// @Tags user -// @Produce json -// @Success 200 {object} []models.User -// @Failure 500 {string} string "failed to get all users" -// @Router /api/v1/users/ [get] -func (u *UserController) GetAllUsers(c *fiber.Ctx) error { - users, err := u.userService.GetAllUsers() - if err != nil { - return err.FiberError(c) - } - - return c.Status(fiber.StatusOK).JSON(users) -} - // Create User godoc // // @Summary Creates a User @@ -51,7 +34,7 @@ func (u *UserController) CreateUser(c *fiber.Ctx) error { var userBody models.CreateUserRequestBody if err := c.BodyParser(&userBody); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } user, err := u.userService.CreateUser(userBody) @@ -62,6 +45,28 @@ func (u *UserController) CreateUser(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(user) } +// GetAllUsers godoc +// +// @Summary Gets all users +// @Description Returns all users +// @ID get-all-users +// @Tags user +// @Produce json +// @Success 200 {object} []models.User +// @Failure 500 {string} string "failed to get all users" +// @Router /api/v1/users/ [get] +func (u *UserController) GetUsers(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + categories, err := u.userService.GetUsers(c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&categories) +} + // GetUser godoc // // @Summary Gets a user @@ -76,7 +81,7 @@ func (u *UserController) CreateUser(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to get user" // @Router /api/v1/users/:id [get] func (u *UserController) GetUser(c *fiber.Ctx) error { - user, err := u.userService.GetUser(c.Params("id")) + user, err := u.userService.GetUser(c.Params("userID")) if err != nil { return err.FiberError(c) } @@ -102,10 +107,10 @@ func (u *UserController) UpdateUser(c *fiber.Ctx) error { var user models.UpdateUserRequestBody if err := c.BodyParser(&user); err != nil { - return errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToParseRequestBody}.FiberError(c) + return errors.FailedToParseRequestBody.FiberError(c) } - updatedUser, err := u.userService.UpdateUser(c.Params("id"), user) + updatedUser, err := u.userService.UpdateUser(c.Params("userID"), user) if err != nil { return err.FiberError(c) } @@ -125,7 +130,7 @@ func (u *UserController) UpdateUser(c *fiber.Ctx) error { // @Failure 500 {string} string "failed to get all users" // @Router /api/v1/users/:id [delete] func (u *UserController) DeleteUser(c *fiber.Ctx) error { - err := u.userService.DeleteUser(c.Params("id")) + err := u.userService.DeleteUser(c.Params("userID")) if err != nil { return err.FiberError(c) } diff --git a/backend/src/controllers/user_tag.go b/backend/src/controllers/user_tag.go new file mode 100644 index 000000000..eb1f7bd0b --- /dev/null +++ b/backend/src/controllers/user_tag.go @@ -0,0 +1,38 @@ +package controllers + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +type UserTagController struct { + userTagService services.UserTagServiceInterface +} + +func NewUserTagController(userTagService services.UserTagServiceInterface) *UserTagController { + return &UserTagController{userTagService: userTagService} +} + +func (u *UserTagController) GetUserTags(c *fiber.Ctx) error { + tags, err := u.userTagService.GetUserTags(c.Params("userID")) + if err != nil { + return err.FiberError(c) + } + return c.Status(fiber.StatusOK).JSON(&tags) +} + +func (u *UserTagController) CreateUserTags(c *fiber.Ctx) error { + var requestBody models.CreateUserTagsBody + if err := c.BodyParser(&requestBody); err != nil { + return errors.FailedToParseRequestBody.FiberError(c) + } + + tags, err := u.userTagService.CreateUserTags(c.Params("userID"), requestBody) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusCreated).JSON(&tags) +} diff --git a/backend/src/database/db.go b/backend/src/database/db.go index 3142c3e39..155b0bb5f 100644 --- a/backend/src/database/db.go +++ b/backend/src/database/db.go @@ -1,7 +1,6 @@ package database import ( - "github.com/GenerateNU/sac/backend/src/auth" "github.com/GenerateNU/sac/backend/src/config" "github.com/GenerateNU/sac/backend/src/models" @@ -11,17 +10,44 @@ import ( ) func ConfigureDB(settings config.Settings) (*gorm.DB, error) { - db, err := gorm.Open(postgres.Open(settings.Database.WithDb()), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), + db, err := EstablishConn(settings.Database.WithDb(), WithLoggerInfo()) + if err != nil { + return nil, err + } + + if err := MigrateDB(settings, db); err != nil { + return nil, err + } + + return db, nil +} + +type OptionalFunc func(gorm.Config) gorm.Config + +func WithLoggerInfo() OptionalFunc { + return func(gormConfig gorm.Config) gorm.Config { + gormConfig.Logger = logger.Default.LogMode(logger.Info) + return gormConfig + } +} + +func EstablishConn(dsn string, opts ...OptionalFunc) (*gorm.DB, error) { + rootConfig := gorm.Config{ SkipDefaultTransaction: true, TranslateError: true, - }) + } + for _, opt := range opts { + rootConfig = opt(rootConfig) + } + + db, err := gorm.Open(postgres.Open(dsn), &rootConfig) if err != nil { return nil, err } - if err := MigrateDB(settings, db); err != nil { + err = db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error + if err != nil { return nil, err } @@ -30,7 +56,6 @@ func ConfigureDB(settings config.Settings) (*gorm.DB, error) { func ConnPooling(db *gorm.DB) error { sqlDB, err := db.DB() - if err != nil { return err } @@ -52,8 +77,8 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error { &models.Tag{}, &models.User{}, &models.File{}, + &models.Membership{}, ) - if err != nil { return err } @@ -76,23 +101,12 @@ func createSuperUser(settings config.Settings, db *gorm.DB) error { return err } - passwordHash, err := auth.ComputePasswordHash(settings.SuperUser.Password) - + superUser, err := SuperUser(settings.SuperUser) if err != nil { + tx.Rollback() return err } - superUser := models.User{ - Role: models.Super, - NUID: "000000000", - Email: "generatesac@gmail.com", - PasswordHash: *passwordHash, - FirstName: "SAC", - LastName: "Super", - College: models.KCCS, - Year: models.First, - } - var user models.User if err := db.Where("nuid = ?", superUser.NUID).First(&user).Error; err != nil { @@ -107,28 +121,27 @@ func createSuperUser(settings config.Settings, db *gorm.DB) error { return err } - superClub := models.Club{ - Name: "SAC", - Preview: "SAC", - Description: "SAC", - } + SuperUserUUID = superUser.ID + + superClub := SuperClub() + if err := tx.Create(&superClub).Error; err != nil { tx.Rollback() return err } - if err := tx.Model(&superClub).Association("Member").Append(&superUser); err != nil { - tx.Rollback() - return err + membership := models.Membership{ + ClubID: superClub.ID, + UserID: superUser.ID, + MembershipType: models.MembershipTypeAdmin, } - if err := tx.Model(&superClub).Update("num_members", gorm.Expr("num_members + ?", 1)).Error; err != nil { + if err := tx.Create(&membership).Error; err != nil { tx.Rollback() return err } return tx.Commit().Error - } return nil } diff --git a/backend/src/database/super.go b/backend/src/database/super.go new file mode 100644 index 000000000..93c065b66 --- /dev/null +++ b/backend/src/database/super.go @@ -0,0 +1,43 @@ +package database + +import ( + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" +) + +var SuperUserUUID uuid.UUID + +func SuperUser(superUserSettings config.SuperUserSettings) (*models.User, *errors.Error) { + passwordHash, err := auth.ComputePasswordHash(superUserSettings.Password.Expose()) + if err != nil { + return nil, &errors.FailedToComputePasswordHash + } + + return &models.User{ + Role: models.Super, + NUID: "000000000", + Email: "generatesac@gmail.com", + PasswordHash: *passwordHash, + FirstName: "SAC", + LastName: "Super", + College: models.KCCS, + Year: models.First, + }, nil +} + +func SuperClub() models.Club { + return models.Club{ + Name: "SAC", + Preview: "SAC", + Description: "SAC", + NumMembers: 0, + IsRecruiting: true, + RecruitmentCycle: models.Always, + RecruitmentType: models.Application, + ApplicationLink: "https://generatenu.com/apply", + Logo: "https://aws.amazon.com/s3", + } +} diff --git a/backend/src/errors/auth.go b/backend/src/errors/auth.go new file mode 100644 index 000000000..e53e5f03d --- /dev/null +++ b/backend/src/errors/auth.go @@ -0,0 +1,14 @@ +package errors + +import "github.com/gofiber/fiber/v2" + +var ( + PassedAuthenticateMiddlewareButNilClaims = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "passed authenticate middleware but claims is nil", + } + FailedToCastToCustomClaims = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to cast to custom claims", + } +) diff --git a/backend/src/errors/category.go b/backend/src/errors/category.go index 9fbe05024..cc3d4672c 100644 --- a/backend/src/errors/category.go +++ b/backend/src/errors/category.go @@ -1,9 +1,38 @@ package errors -const ( - FailedToValidateCategory = "failed to validate category" - FailedToCreateCategory = "failed to create category" - FailedToGetCategory = "failed to get category" - CategoryAlreadyExists = "category already exists" - CategoryNotFound = "category not found" +import "github.com/gofiber/fiber/v2" + +var ( + FailedToValidateCategory = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate category", + } + FailedToCreateCategory = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create category", + } + FailedToGetCategories = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get categories", + } + FailedToGetCategory = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get category", + } + FailedToUpdateCategory = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to update category", + } + FailedToDeleteCategory = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete category", + } + CategoryAlreadyExists = Error{ + StatusCode: fiber.StatusConflict, + Message: "category already exists", + } + CategoryNotFound = Error{ + StatusCode: fiber.StatusNotFound, + Message: "category not found", + } ) diff --git a/backend/src/errors/club.go b/backend/src/errors/club.go index 04b32187b..7b05d03bd 100644 --- a/backend/src/errors/club.go +++ b/backend/src/errors/club.go @@ -1 +1,42 @@ package errors + +import "github.com/gofiber/fiber/v2" + +var ( + FailedToValidateUserID = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate user id", + } + FailedToValidateClub = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate club", + } + FailedToCreateClub = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create club", + } + FailedToGetClubs = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get clubs", + } + FailedToGetClub = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get club", + } + FailedToDeleteClub = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete club", + } + FailedToUpdateClub = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to update club", + } + ClubNotFound = Error{ + StatusCode: fiber.StatusNotFound, + Message: "club not found", + } + FailedtoGetAdminIDs = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get admin ids", + } +) diff --git a/backend/src/errors/common.go b/backend/src/errors/common.go index 69fbc5db4..343594d5b 100644 --- a/backend/src/errors/common.go +++ b/backend/src/errors/common.go @@ -1,8 +1,66 @@ package errors -const ( - FailedToParseRequestBody = "failed to parse request body" - FailedToValidateID = "failed to validate id" - FailedToMapResposeToModel = "failed to map response to model" - InternalServerError = "internal server error" +import "github.com/gofiber/fiber/v2" + +var ( + FailedToParseRequestBody = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to parse request body", + } + FailedToValidateID = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate id", + } + FailedToValidateNonNegativeValue = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate non-negative value", + } + FailedToMapRequestToModel = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to map request to model", + } + InternalServerError = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "internal server error", + } + FailedToValidateLimit = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate limit", + } + FailedToValidatePage = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate page", + } + Unauthorized = Error{ + StatusCode: fiber.StatusUnauthorized, + Message: "unauthorized", + } + FailedToSignToken = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to sign token", + } + FailedToCreateAccessToken = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create access token", + } + FailedToCreateRefreshToken = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create refresh token", + } + FailedToParseRefreshToken = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to parse refresh token", + } + FailedToParseAccessToken = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to parse access token", + } + FailedToValidateRefreshToken = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate refresh token", + } + FailedToValidateAccessToken = Error{ + StatusCode: fiber.StatusUnauthorized, + Message: "failed to validate access token", + } ) diff --git a/backend/src/errors/contact.go b/backend/src/errors/contact.go new file mode 100644 index 000000000..d408a5a37 --- /dev/null +++ b/backend/src/errors/contact.go @@ -0,0 +1,30 @@ +package errors + +import "github.com/gofiber/fiber/v2" + +var ( + FailedToGetContacts = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get contacts", + } + FailedToGetContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get contact", + } + ContactNotFound = Error{ + StatusCode: fiber.StatusNotFound, + Message: "contact not found", + } + FailedToPutContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to put contact", + } + FailedToDeleteContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete contact", + } + FailedToValidateContact = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate contact", + } +) diff --git a/backend/src/errors/error.go b/backend/src/errors/error.go index c59c20262..ee5035f4e 100644 --- a/backend/src/errors/error.go +++ b/backend/src/errors/error.go @@ -9,10 +9,10 @@ type Error struct { Message string } -func (e Error) FiberError(c *fiber.Ctx) error { +func (e *Error) FiberError(c *fiber.Ctx) error { return c.Status(e.StatusCode).JSON(fiber.Map{"error": e.Message}) } -func (e Error) Error() string { +func (e *Error) Error() string { return e.Message } diff --git a/backend/src/errors/file.go b/backend/src/errors/file.go index a3cc2d754..573ec4342 100644 --- a/backend/src/errors/file.go +++ b/backend/src/errors/file.go @@ -1,14 +1,46 @@ package errors -const ( - FailedToValidateFileId = "failed to validate file id" - InvalidFileSize = "file size is greater than 5 MB" - FailedToCreateAWSSession = "failed to create AWS session" - FailedToUploadToS3 = "failed to upload to S3 Bucket" - FailedToCreateFileInDB = "failed to create file in database" - FailedToGetFile = "failed to get file" - FailedToProcessRequest = "failed to process the request" - FailedToValidatedData = "failed to validate data" - FailedToOpenFile = "failed to open file" - InvalidImageFormat = "invalid image format" +import "github.com/gofiber/fiber/v2" + +var ( + FailedToValidateFileId = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to validate file id", + } + InvalidFileSize = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "file size is greater than 5 MB", + } + FailedToCreateAWSSession = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create AWS session", + } + FailedToUploadToS3 = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to upload to S3 Bucket", + } + FailedToCreateFileInDB = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create file in database", + } + FailedToGetFile = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to get file", + } + FailedToProcessRequest = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to process the request", + } + FailedToValidatedData = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate data", + } + FailedToOpenFile = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to open file", + } + InvalidImageFormat = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "invalid image format", + } ) diff --git a/backend/src/errors/point_of_contact.go b/backend/src/errors/point_of_contact.go index fbe892ae6..89a1be598 100644 --- a/backend/src/errors/point_of_contact.go +++ b/backend/src/errors/point_of_contact.go @@ -1,16 +1,42 @@ package errors -const ( - FailedToUpsertPointOfContact = "failed to update or insert point of contact" - FailedToGetAllPointOfContact = "failed to get all point of contact" - PointOfContactNotFound = "point of contact not found" - FailedToDeletePointOfContact = "failed to delete point of contact" - FailedToValidatePointOfContact = "failed to validate point of contact" - FailedToValidateEmail = "failed to validate email" - FailedToMapResponseToModel = "failed to map response to model" - FailedToGetAPointOfContact = "failed to get a point of contact" - FailedToValidatePointOfContactId = "failed to validate point of contact id" - FailedToValidateClubId = "failed to validate club id" - ClubNotFound = "club not found" - FailedToGetClub = "failed to get club" -) \ No newline at end of file +import "github.com/gofiber/fiber/v2" + +var ( + FailedToUpsertPointOfContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to update or insert point of contact", + } + FailedToGetAllPointOfContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get all point of contact", + } + PointOfContactNotFound = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "point of contact not found", + } + FailedToDeletePointOfContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete point of contact", + } + FailedToValidatePointOfContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to validate point of contact", + } + FailedToValidateEmail = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate email", + } + FailedToMapResponseToModel = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to map response to model", + } + FailedToGetAPointOfContact = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get a point of contact", + } + FailedToValidatePointOfContactId = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate point of contact id", + } +) diff --git a/backend/src/errors/tag.go b/backend/src/errors/tag.go index da1caf1b9..9e67a0a7c 100644 --- a/backend/src/errors/tag.go +++ b/backend/src/errors/tag.go @@ -1,10 +1,34 @@ package errors -const ( - FailedToValidateTag = "failed to validate tag" - FailedToCreateTag = "failed to create tag" - FailedToGetTag = "failed to get tag" - FailedToUpdateTag = "failed to update tag" - FailedToDeleteTag = "failed to delete tag" - TagNotFound = "tag not found" +import "github.com/gofiber/fiber/v2" + +var ( + FailedToValidateTag = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate tag", + } + FailedToCreateTag = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create tag", + } + FailedToGetTags = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get tags", + } + FailedToGetTag = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get tag", + } + FailedToUpdateTag = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to update tag", + } + FailedToDeleteTag = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete tag", + } + TagNotFound = Error{ + StatusCode: fiber.StatusNotFound, + Message: "tag not found", + } ) diff --git a/backend/src/errors/user.go b/backend/src/errors/user.go index c1c843ba3..de07afbb7 100644 --- a/backend/src/errors/user.go +++ b/backend/src/errors/user.go @@ -1,12 +1,46 @@ package errors -const ( - FailedToValidateUser = "failed to validate user" - FailedToGetAllUsers = "failed to get all users" - FailedToGetUser = "failed to get user" - FailedToCreateUser = "failed to create user" - FailedToUpdateUser = "failed to update user" - FailedToDeleteUser = "failed to delete user" - UserAlreadyExists = "user already exists" - UserNotFound = "user not found" +import "github.com/gofiber/fiber/v2" + +var ( + FailedToValidateUser = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate user", + } + FailedToValidateUserTags = Error{ + StatusCode: fiber.StatusBadRequest, + Message: "failed to validate user tags", + } + FailedToCreateUser = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to create user", + } + FailedToUpdateUser = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to update user", + } + FailedToGetUsers = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get users", + } + FailedToGetUser = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get user", + } + FailedToDeleteUser = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to delete user", + } + UserAlreadyExists = Error{ + StatusCode: fiber.StatusConflict, + Message: "user already exists", + } + UserNotFound = Error{ + StatusCode: fiber.StatusNotFound, + Message: "user not found", + } + FailedToComputePasswordHash = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to compute password hash", + } ) diff --git a/backend/src/main.go b/backend/src/main.go index f831c05d8..f5b1ffcc3 100644 --- a/backend/src/main.go +++ b/backend/src/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "path/filepath" "github.com/GenerateNU/sac/backend/src/config" "github.com/GenerateNU/sac/backend/src/database" @@ -19,7 +20,7 @@ import ( // @BasePath /api/v1 func main() { onlyMigrate := flag.Bool("only-migrate", false, "Specify if you want to only perform the database migration") - configPath := flag.String("config", "../../config", "Specify the path to the config directory") + configPath := flag.String("config", filepath.Join("..", "..", "config"), "Specify the path to the config directory") flag.Parse() @@ -28,7 +29,7 @@ func main() { panic(fmt.Sprintf("Error getting configuration: %s", err.Error())) } - db, err := database.ConfigureDB(config) + db, err := database.ConfigureDB(*config) if err != nil { panic(fmt.Sprintf("Error configuring database: %s", err.Error())) } @@ -39,10 +40,13 @@ func main() { err = database.ConnPooling(db) if err != nil { - panic(err) + panic(fmt.Sprintf("Error connecting to database: %s", err.Error())) } - app := server.Init(db, config) + app := server.Init(db, *config) - app.Listen(fmt.Sprintf("%s:%d", config.Application.Host, config.Application.Port)) + err = app.Listen(fmt.Sprintf("%s:%d", config.Application.Host, config.Application.Port)) + if err != nil { + panic(fmt.Sprintf("Error starting server: %s", err.Error())) + } } diff --git a/backend/src/middleware/auth.go b/backend/src/middleware/auth.go new file mode 100644 index 000000000..dc49e8f66 --- /dev/null +++ b/backend/src/middleware/auth.go @@ -0,0 +1,86 @@ +package middleware + +import ( + "slices" + + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/types" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/skip" +) + +var paths = []string{ + "/api/v1/auth/login", + "/api/v1/auth/refresh", + "/api/v1/users/", + "/api/v1/auth/logout", +} + +func SuperSkipper(h fiber.Handler) fiber.Handler { + return skip.New(h, func(c *fiber.Ctx) bool { + claims, err := types.From(c) + if err != nil { + _ = err.FiberError(c) + return false + } + if claims == nil { + return false + } + return claims.Role == string(models.Super) + }) +} + +func (m *MiddlewareService) Authenticate(c *fiber.Ctx) error { + if slices.Contains(paths, c.Path()) { + return c.Next() + } + + token, err := auth.ParseAccessToken(c.Cookies("access_token"), m.AuthSettings.AccessKey) + if err != nil { + return errors.FailedToParseAccessToken.FiberError(c) + } + + claims, ok := token.Claims.(*types.CustomClaims) + if !ok || !token.Valid { + return errors.FailedToValidateAccessToken.FiberError(c) + } + + if auth.IsBlacklisted(c.Cookies("access_token")) { + return errors.Unauthorized.FiberError(c) + } + + c.Locals("claims", claims) + + return c.Next() +} + +func (m *MiddlewareService) Authorize(requiredPermissions ...types.Permission) func(c *fiber.Ctx) error { + return func(c *fiber.Ctx) error { + claims, fromErr := types.From(c) + if fromErr != nil { + return fromErr.FiberError(c) + } + + if claims != nil && claims.Role == string(models.Super) { + return c.Next() + } + + role, err := auth.GetRoleFromToken(c.Cookies("access_token"), m.AuthSettings.AccessKey) + if err != nil { + return errors.FailedToParseAccessToken.FiberError(c) + } + + userPermissions := types.GetPermissions(models.UserRole(*role)) + + for _, requiredPermission := range requiredPermissions { + if !slices.Contains(userPermissions, requiredPermission) { + return errors.Unauthorized.FiberError(c) + } + } + + return c.Next() + } +} diff --git a/backend/src/middleware/club.go b/backend/src/middleware/club.go new file mode 100644 index 000000000..dc848b75d --- /dev/null +++ b/backend/src/middleware/club.go @@ -0,0 +1,47 @@ +package middleware + +import ( + "slices" + + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/gofiber/fiber/v2" +) + +func (m *MiddlewareService) ClubAuthorizeById(c *fiber.Ctx) error { + clubUUID, err := utilities.ValidateID(c.Params("clubID")) + if err != nil { + return errors.FailedToValidateID.FiberError(c) + } + + token, tokenErr := auth.ParseAccessToken(c.Cookies("access_token"), m.AuthSettings.AccessKey) + if tokenErr != nil { + return errors.FailedToParseAccessToken.FiberError(c) + } + + claims, ok := token.Claims.(*types.CustomClaims) + if !ok || !token.Valid { + return errors.FailedToValidateAccessToken.FiberError(c) + } + + issuerUUID, issueErr := utilities.ValidateID(claims.Issuer) + if issueErr != nil { + return errors.FailedToParseAccessToken.FiberError(c) + } + + // use club_id to get the list of admin for a certain club + clubAdmin, clubErr := transactions.GetAdminIDs(m.DB, *clubUUID) + if clubErr != nil { + return err + } + + // check issuerID against the list of admin for the certain club + if slices.Contains(clubAdmin, *issuerUUID) { + return c.Next() + } + + return errors.Unauthorized.FiberError(c) +} diff --git a/backend/src/middleware/middleware.go b/backend/src/middleware/middleware.go new file mode 100644 index 000000000..3f1169083 --- /dev/null +++ b/backend/src/middleware/middleware.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +type MiddlewareInterface interface { + ClubAuthorizeById(c *fiber.Ctx) error + UserAuthorizeById(c *fiber.Ctx) error + Authenticate(c *fiber.Ctx) error + Authorize(requiredPermissions ...types.Permission) func(c *fiber.Ctx) error +} + +type MiddlewareService struct { + DB *gorm.DB + Validate *validator.Validate + AuthSettings config.AuthSettings +} + +func NewMiddlewareService(db *gorm.DB, validate *validator.Validate, authSettings config.AuthSettings) *MiddlewareService { + return &MiddlewareService{ + DB: db, + Validate: validate, + AuthSettings: authSettings, + } +} diff --git a/backend/src/middleware/user.go b/backend/src/middleware/user.go new file mode 100644 index 000000000..804a1295b --- /dev/null +++ b/backend/src/middleware/user.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/gofiber/fiber/v2" +) + +func (m *MiddlewareService) UserAuthorizeById(c *fiber.Ctx) error { + idAsUUID, err := utilities.ValidateID(c.Params("userID")) + if err != nil { + return errors.FailedToValidateID.FiberError(c) + } + + token, tokenErr := auth.ParseAccessToken(c.Cookies("access_token"), m.AuthSettings.AccessKey) + if tokenErr != nil { + return err + } + + claims, ok := token.Claims.(*types.CustomClaims) + if !ok || !token.Valid { + return errors.FailedToValidateAccessToken.FiberError(c) + } + + issuerIDAsUUID, err := utilities.ValidateID(claims.Issuer) + if err != nil { + return errors.FailedToValidateID.FiberError(c) + } + + if issuerIDAsUUID.String() == idAsUUID.String() { + return c.Next() + } + + return errors.Unauthorized.FiberError(c) +} diff --git a/backend/src/migrations/data.sql b/backend/src/migrations/data.sql index 45bf596a9..c53bc1918 100644 --- a/backend/src/migrations/data.sql +++ b/backend/src/migrations/data.sql @@ -1,8 +1,9 @@ -- BEGIN MOCK DATA TRANSACTION BEGIN; -INSERT INTO users (role, nuid, email, password_hash, first_name, last_name, college, year) VALUES ('super', '002183108', 'oduneye.d@northeastern.edu', 'rust', 'David', 'Oduneye', 'KCCS', 3); -INSERT INTO users (role, nuid, email, password_hash, first_name, last_name, college, year) VALUES ('super', '002172052', 'ladley.g@northeastern.edu', 'rust', 'Garrett', 'Ladley', 'KCCS', 3); - +INSERT INTO users (id, role, nuid, email, password_hash, first_name, last_name, college, year) VALUES ('29cac84a-362c-4ffa-9f4c-2f76057b7902', 'super', '002183108', 'oduneye.d@northeastern.edu', '$argon2id$v=19$m=65536,t=3,p=2$zYyFSnLvC5Q482mzMJrTjg$WUhpXwulvfipyWg7asQyCRUqBEnjizDOoMP2/GvWQR8', 'David', 'Oduneye', 'KCCS', 3); +INSERT INTO users (id, role, nuid, email, password_hash, first_name, last_name, college, year) VALUES ('4f4d9990-7d26-4229-911d-1aa61851c292', 'super', '002172052', 'ladley.g@northeastern.edu', '$argon2id$v=19$m=65536,t=3,p=2$zYyFSnLvC5Q482mzMJrTjg$WUhpXwulvfipyWg7asQyCRUqBEnjizDOoMP2/GvWQR8', 'Garrett', 'Ladley', 'KCCS', 3); +INSERT INTO user_club_members (user_id, club_id, membership_type) VALUES ('29cac84a-362c-4ffa-9f4c-2f76057b7902', (SELECT id FROM clubs WHERE name = 'SAC'), 'admin'); +INSERT INTO user_club_members (user_id, club_id, membership_type) VALUES ('4f4d9990-7d26-4229-911d-1aa61851c292', (SELECT id FROM clubs WHERE name = 'SAC'), 'admin'); COMMIT; -- END MOCK DATA TRANSACTION diff --git a/backend/src/models/category.go b/backend/src/models/category.go index 7368e3c67..6f9770557 100644 --- a/backend/src/models/category.go +++ b/backend/src/models/category.go @@ -1,14 +1,12 @@ package models -import "github.com/GenerateNU/sac/backend/src/types" - type Category struct { - types.Model + Model - Name string `gorm:"type:varchar(255);unique" json:"category_name" validate:"required,max=255"` + Name string `gorm:"type:varchar(255);unique" json:"name" validate:"required,max=255"` Tag []Tag `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` } type CategoryRequestBody struct { - Name string `json:"category_name" validate:"required,max=255"` + Name string `json:"name" validate:"required,max=255"` } diff --git a/backend/src/models/club.go b/backend/src/models/club.go index b512fa3ee..3c56614d6 100644 --- a/backend/src/models/club.go +++ b/backend/src/models/club.go @@ -1,9 +1,8 @@ package models import ( - "time" - - "github.com/GenerateNU/sac/backend/src/types" + "github.com/google/uuid" + "gorm.io/gorm" ) type RecruitmentCycle string @@ -24,23 +23,24 @@ const ( ) type Club struct { - types.Model + Model - SoftDeletedAt time.Time `gorm:"type:timestamptz;default:NULL" json:"-" validate:"-"` + SoftDeletedAt gorm.DeletedAt `gorm:"type:timestamptz;default:NULL" json:"-" validate:"-"` Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` Preview string `gorm:"type:varchar(255)" json:"preview" validate:"required,max=255"` - Description string `gorm:"type:varchar(255)" json:"description" validate:"required,url,max=255"` // MongoDB URL + Description string `gorm:"type:varchar(255)" json:"description" validate:"required,http_url,mongo_url,max=255"` // MongoDB URL NumMembers int `gorm:"type:int" json:"num_members" validate:"required,min=1"` IsRecruiting bool `gorm:"type:bool;default:false" json:"is_recruiting" validate:"required"` - RecruitmentCycle RecruitmentCycle `gorm:"type:varchar(255);default:always" json:"recruitment_cycle" validate:"required,max=255"` - RecruitmentType RecruitmentType `gorm:"type:varchar(255);default:unrestricted" json:"recruitment_type" validate:"required,max=255"` - ApplicationLink string `gorm:"type:varchar(255);default:NULL" json:"application_link" validate:"required,max=255"` - Logo string `gorm:"type:varchar(255);default:NULL" json:"logo" validate:"url,max=255"` // S3 URL + RecruitmentCycle RecruitmentCycle `gorm:"type:varchar(255);default:always" json:"recruitment_cycle" validate:"required,max=255,oneof=fall spring fallSpring always"` + RecruitmentType RecruitmentType `gorm:"type:varchar(255);default:unrestricted" json:"recruitment_type" validate:"required,max=255,oneof=unrestricted tryout application"` + ApplicationLink string `gorm:"type:varchar(255);default:NULL" json:"application_link" validate:"required,max=255,http_url"` + Logo string `gorm:"type:varchar(255);default:NULL" json:"logo" validate:"omitempty,http_url,s3_url,max=255"` // S3 URL - Parent *uint `gorm:"foreignKey:Parent" json:"-" validate:"min=1"` - Tag []Tag `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` + Parent *uuid.UUID `gorm:"foreignKey:Parent" json:"-" validate:"uuid4"` + Tag []Tag `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` // User + Admin []User `gorm:"many2many:user_club_admins;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"required"` Member []User `gorm:"many2many:user_club_members;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"required"` Follower []User `gorm:"many2many:user_club_followers;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` IntendedApplicant []User `gorm:"many2many:user_club_intended_applicants;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` @@ -51,3 +51,36 @@ type Club struct { Event []Event `gorm:"many2many:club_events;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Notifcation []Notification `gorm:"polymorphic:Reference;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` } + +type CreateClubRequestBody struct { + UserID uuid.UUID `json:"user_id" validate:"required,uuid4"` + Name string `json:"name" validate:"required,max=255"` + Preview string `json:"preview" validate:"required,max=255"` + Description string `json:"description" validate:"required,http_url,mongo_url,max=255"` // MongoDB URL + IsRecruiting bool `json:"is_recruiting" validate:"required"` + RecruitmentCycle RecruitmentCycle `gorm:"type:varchar(255);default:always" json:"recruitment_cycle" validate:"required,max=255,oneof=fall spring fallSpring always"` + RecruitmentType RecruitmentType `gorm:"type:varchar(255);default:unrestricted" json:"recruitment_type" validate:"required,max=255,oneof=unrestricted tryout application"` + ApplicationLink string `json:"application_link" validate:"required,max=255,http_url"` + Logo string `json:"logo" validate:"omitempty,http_url,s3_url,max=255"` // S3 URL +} + +type UpdateClubRequestBody struct { + Name string `json:"name" validate:"omitempty,max=255"` + Preview string `json:"preview" validate:"omitempty,max=255"` + Description string `json:"description" validate:"omitempty,http_url,mongo_url,max=255"` // MongoDB URL + IsRecruiting bool `json:"is_recruiting" validate:"omitempty"` + RecruitmentCycle RecruitmentCycle `gorm:"type:varchar(255);default:always" json:"recruitment_cycle" validate:"required,max=255,oneof=fall spring fallSpring always"` + RecruitmentType RecruitmentType `gorm:"type:varchar(255);default:unrestricted" json:"recruitment_type" validate:"required,max=255,oneof=unrestricted tryout application"` + ApplicationLink string `json:"application_link" validate:"omitempty,required,max=255,http_url"` + Logo string `json:"logo" validate:"omitempty,http_url,s3_url,max=255"` // S3 URL +} + +func (c *Club) AfterCreate(tx *gorm.DB) (err error) { + tx.Model(&c).Update("num_members", c.NumMembers+1) + return +} + +func (c *Club) AfterDelete(tx *gorm.DB) (err error) { + tx.Model(&c).Update("num_members", c.NumMembers-1) + return +} diff --git a/backend/src/models/comment.go b/backend/src/models/comment.go index 043c42d15..9e2bb9eb1 100644 --- a/backend/src/models/comment.go +++ b/backend/src/models/comment.go @@ -1,22 +1,20 @@ package models -import ( - "github.com/GenerateNU/sac/backend/src/types" -) +import "github.com/google/uuid" type Comment struct { - types.Model + Model Question string `gorm:"type:varchar(255)" json:"question" validate:"required,max=255"` Answer string `gorm:"type:varchar(255)" json:"answer" validate:",max=255"` NumFoundHelpful uint `gorm:"type:int;default:0" json:"num_found_helpful" validate:"min=0"` - AskedByID uint `gorm:"type:uuid" json:"-" validate:"min=1"` - AskedBy User `gorm:"foreignKey:AskedByID" json:"-" validate:"-"` + AskedByID uuid.UUID `gorm:"type:uuid" json:"-" validate:"uuid4"` + AskedBy User `gorm:"foreignKey:AskedByID" json:"-" validate:"-"` - ClubID uint `gorm:"type:uuid" json:"-" validate:"min=1"` - Club Club `gorm:"foreignKey:ClubID" json:"-" validate:"-"` + ClubID uuid.UUID `gorm:"type:uuid" json:"-" validate:"uuid4"` + Club Club `gorm:"foreignKey:ClubID" json:"-" validate:"-"` - AnsweredByID *uint `gorm:"type:uuid" json:"-" validate:"min=1"` - AnsweredBy *User `gorm:"foreignKey:AnsweredBy" json:"-" validate:"-"` + AnsweredByID *uuid.UUID `gorm:"type:uuid" json:"-" validate:"uuid4"` + AnsweredBy *User `gorm:"foreignKey:AnsweredBy" json:"-" validate:"-"` } diff --git a/backend/src/models/contact.go b/backend/src/models/contact.go index 5e4f74c0b..3ab4baa30 100644 --- a/backend/src/models/contact.go +++ b/backend/src/models/contact.go @@ -1,26 +1,32 @@ package models -import ( - "github.com/GenerateNU/sac/backend/src/types" -) +import "github.com/google/uuid" -type Media string +type ContactType string const ( - Facebook Media = "facebook" - Instagram Media = "instagram" - Twitter Media = "twitter" - LinkedIn Media = "linkedin" - YouTube Media = "youtube" - GitHub Media = "github" - Custom Media = "custom" + Facebook ContactType = "facebook" + Instagram ContactType = "instagram" + Twitter ContactType = "twitter" + LinkedIn ContactType = "linkedin" + YouTube ContactType = "youtube" + GitHub ContactType = "github" + Slack ContactType = "slack" + Discord ContactType = "discord" + Email ContactType = "email" + CustomSite ContactType = "customSite" ) type Contact struct { - types.Model + Model + + Type ContactType `gorm:"type:varchar(255);uniqueIndex:idx_contact_type" json:"type" validate:"required,max=255,oneof=facebook instagram twitter linkedin youtube github slack discord email customSite"` + Content string `gorm:"type:varchar(255)" json:"content" validate:"required,max=255"` - Type Media `gorm:"type:varchar(255)" json:"type" validate:"required,max=255"` - Content string `gorm:"type:varchar(255)" json:"content" validate:"required,url,max=255"` // media URL + ClubID uuid.UUID `gorm:"foreignKey:ClubID;uniqueIndex:idx_contact_type" json:"-" validate:"uuid4"` +} - ClubID uint `gorm:"foreignKey:ClubID" json:"-" validate:"min=1"` +type PutContactRequestBody struct { + Type ContactType `json:"type" validate:"required,max=255,oneof=facebook instagram twitter linkedin youtube github slack discord email customSite,contact_pointer"` + Content string `json:"content" validate:"required,max=255"` } diff --git a/backend/src/models/event.go b/backend/src/models/event.go index 075e47e1f..ad155ef39 100644 --- a/backend/src/models/event.go +++ b/backend/src/models/event.go @@ -2,8 +2,6 @@ package models import ( "time" - - "github.com/GenerateNU/sac/backend/src/types" ) type EventType string @@ -14,7 +12,7 @@ const ( ) type Event struct { - types.Model + Model Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` Preview string `gorm:"type:varchar(255)" json:"preview" validate:"required,max=255"` diff --git a/backend/src/models/file.go b/backend/src/models/file.go index ccf309e34..82b052624 100644 --- a/backend/src/models/file.go +++ b/backend/src/models/file.go @@ -1,11 +1,8 @@ package models -import ( - "github.com/GenerateNU/sac/backend/src/types" -) - type File struct { - types.Model + Model + FileName string `gorm:"type:varchar(255)" json:"file_name"` FileSize int64 `gorm:"type:bigint;default:0" json:"file_size"` ObjectKey string `gorm:"type:varchar(255);unique" json:"object_key"` @@ -13,4 +10,4 @@ type File struct { ClubID uint `gorm:"foreignKey:ClubID;" json:"-" validate:"min=1"` UserID uint `gorm:"foreignKey:UserID;" json:"-" validate:"min=1"` -} \ No newline at end of file +} diff --git a/backend/src/models/membership.go b/backend/src/models/membership.go new file mode 100644 index 000000000..5ffc69d34 --- /dev/null +++ b/backend/src/models/membership.go @@ -0,0 +1,30 @@ +package models + +import "github.com/google/uuid" + +type MembershipType string + +const ( + MembershipTypeMember MembershipType = "member" + MembershipTypeAdmin MembershipType = "admin" +) + +type Tabler interface { + TableName() string +} + +func (Membership) TableName() string { + return "user_club_members" +} + +type Membership struct { + Model + + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id" validate:"required,uuid4"` + ClubID uuid.UUID `gorm:"type:uuid;not null" json:"club_id" validate:"required,uuid4"` + + Club *Club `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` + User *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` + + MembershipType MembershipType `gorm:"type:varchar(255);not null;default:member" json:"membership_type" validate:"required,oneof=member admin"` +} diff --git a/backend/src/models/notification.go b/backend/src/models/notification.go index cc15ab032..fd07e4580 100644 --- a/backend/src/models/notification.go +++ b/backend/src/models/notification.go @@ -3,7 +3,7 @@ package models import ( "time" - "github.com/GenerateNU/sac/backend/src/types" + "github.com/google/uuid" ) type NotificationType string @@ -14,14 +14,14 @@ const ( ) type Notification struct { - types.Model + Model SendAt time.Time `gorm:"type:timestamptz" json:"send_at" validate:"required"` Title string `gorm:"type:varchar(255)" json:"title" validate:"required,max=255"` Content string `gorm:"type:varchar(255)" json:"content" validate:"required,max=255"` DeepLink string `gorm:"type:varchar(255)" json:"deep_link" validate:"required,max=255"` - Icon string `gorm:"type:varchar(255)" json:"icon" validate:"required,url,max=255"` // S3 URL + Icon string `gorm:"type:varchar(255)" json:"icon" validate:"required,http_url,max=255"` // S3 URL - ReferenceID uint `gorm:"type:int" json:"-" validate:"min=1"` + ReferenceID uuid.UUID `gorm:"type:int" json:"-" validate:"uuid4"` ReferenceType NotificationType `gorm:"type:varchar(255)" json:"-" validate:"max=255"` } diff --git a/backend/src/models/point_of_contact.go b/backend/src/models/point_of_contact.go index e9db8e0d6..25f1e4509 100644 --- a/backend/src/models/point_of_contact.go +++ b/backend/src/models/point_of_contact.go @@ -1,18 +1,16 @@ package models -import ( - "github.com/GenerateNU/sac/backend/src/types" -) +import "github.com/google/uuid" type PointOfContact struct { - types.Model + Model Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` Email string `gorm:"uniqueIndex:compositeindex;index;not null;type:varchar(255)" json:"email" validate:"required,email,max=255"` Photo string `gorm:"type:varchar(255);default:NULL" json:"photo" validate:"url,max=255"` // S3 URL, fallback to default logo if null Position string `gorm:"type:varchar(255);" json:"position" validate:"required,max=255"` - ClubID uint `gorm:"uniqueIndex:compositeindex;index;not null;foreignKey:ClubID" json:"-" validate:"min=1"` + ClubID uuid.UUID `gorm:"uniqueIndex:compositeindex;index;not null;foreignKey:ClubID" json:"-" validate:"min=1"` } type CreatePointOfContactBody struct { @@ -20,4 +18,4 @@ type CreatePointOfContactBody struct { Email string `json:"email" validate:"required,email,max=255"` Photo string `json:"photo" validate:"omitempty,url,max=255"` // S3 URL, fallback to default logo if null Position string `json:"position" validate:"required,max=255"` -} \ No newline at end of file +} diff --git a/backend/src/types/root_model.go b/backend/src/models/root.go similarity index 61% rename from backend/src/types/root_model.go rename to backend/src/models/root.go index 341b761c9..a1babc13a 100644 --- a/backend/src/types/root_model.go +++ b/backend/src/models/root.go @@ -1,11 +1,13 @@ -package types +package models import ( "time" + + "github.com/google/uuid" ) type Model struct { - ID uint `gorm:"primarykey" json:"id" example:"1"` + ID uuid.UUID `gorm:"type:uuid;primary_key;default:uuid_generate_v4()" json:"id" example:"123e4567-e89b-12d3-a456-426614174000"` CreatedAt time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"created_at" example:"2023-09-20T16:34:50Z"` UpdatedAt time.Time `gorm:"type:timestamp;default:CURRENT_TIMESTAMP" json:"updated_at" example:"2023-09-20T16:34:50Z"` } diff --git a/backend/src/models/tag.go b/backend/src/models/tag.go index 7594c8459..36c71381a 100644 --- a/backend/src/models/tag.go +++ b/backend/src/models/tag.go @@ -1,15 +1,13 @@ package models -import ( - "github.com/GenerateNU/sac/backend/src/types" -) +import "github.com/google/uuid" type Tag struct { - types.Model + Model Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"` - CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required,min=1"` + CategoryID uuid.UUID `json:"category_id" validate:"required,uuid4"` User []User `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` @@ -17,6 +15,6 @@ type Tag struct { } type TagRequestBody struct { - Name string `json:"name" validate:"required,max=255"` - CategoryID uint `json:"category_id" validate:"required,min=1"` -} \ No newline at end of file + Name string `json:"name" validate:"required,max=255"` + CategoryID uuid.UUID `json:"category_id" validate:"required,uuid4"` +} diff --git a/backend/src/models/user.go b/backend/src/models/user.go index 1f6b3aa7e..fefd911b6 100644 --- a/backend/src/models/user.go +++ b/backend/src/models/user.go @@ -1,13 +1,12 @@ package models -import "github.com/GenerateNU/sac/backend/src/types" +import "github.com/google/uuid" type UserRole string -const ( - Super UserRole = "super" - ClubAdmin UserRole = "clubAdmin" - Student UserRole = "student" +var ( + Super UserRole = "super" + Student UserRole = "student" ) type College string @@ -36,9 +35,9 @@ const ( ) type User struct { - types.Model + Model - Role UserRole `gorm:"type:varchar(255);" json:"user_role,omitempty" validate:"required,max=255"` + Role UserRole `gorm:"type:varchar(255);default:'student'" json:"role" validate:"required,oneof=super student"` NUID string `gorm:"column:nuid;type:varchar(9);unique" json:"nuid" validate:"required,numeric,len=9"` FirstName string `gorm:"type:varchar(255)" json:"first_name" validate:"required,max=255"` LastName string `gorm:"type:varchar(255)" json:"last_name" validate:"required,max=255"` @@ -48,6 +47,7 @@ type User struct { Year Year `gorm:"type:smallint" json:"year" validate:"required,min=1,max=6"` Tag []Tag `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` + Admin []Club `gorm:"many2many:user_club_admins;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Member []Club `gorm:"many2many:user_club_members;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Follower []Club `gorm:"many2many:user_club_followers;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` IntendedApplicant []Club `gorm:"many2many:user_club_intended_applicants;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` @@ -72,7 +72,15 @@ type UpdateUserRequestBody struct { FirstName string `json:"first_name" validate:"omitempty,max=255"` LastName string `json:"last_name" validate:"omitempty,max=255"` Email string `json:"email" validate:"omitempty,email,neu_email,max=255"` - Password string `json:"password" validate:"omitempty,password"` College College `json:"college" validate:"omitempty,oneof=CAMD DMSB KCCS CE BCHS SL CPS CS CSSH"` Year Year `json:"year" validate:"omitempty,min=1,max=6"` } + +type LoginUserResponseBody struct { + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"min=8,max=255"` +} + +type CreateUserTagsBody struct { + Tags []uuid.UUID `json:"tags" validate:"required"` +} diff --git a/backend/src/server/routes/auth.go b/backend/src/server/routes/auth.go new file mode 100644 index 000000000..7efe8d127 --- /dev/null +++ b/backend/src/server/routes/auth.go @@ -0,0 +1,20 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func Auth(router fiber.Router, authService services.AuthServiceInterface, authSettings config.AuthSettings) { + authController := controllers.NewAuthController(authService, authSettings) + + // api/v1/auth/* + auth := router.Group("/auth") + + auth.Post("/login", authController.Login) + auth.Get("/logout", authController.Logout) + auth.Get("/refresh", authController.Refresh) + auth.Get("/me", authController.Me) +} diff --git a/backend/src/server/routes/category.go b/backend/src/server/routes/category.go new file mode 100644 index 000000000..1bffb59f2 --- /dev/null +++ b/backend/src/server/routes/category.go @@ -0,0 +1,21 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func Category(router fiber.Router, categoryService services.CategoryServiceInterface) fiber.Router { + categoryController := controllers.NewCategoryController(categoryService) + + categories := router.Group("/categories") + + categories.Post("/", categoryController.CreateCategory) + categories.Get("/", categoryController.GetCategories) + categories.Get("/:categoryID", categoryController.GetCategory) + categories.Delete("/:categoryID", categoryController.DeleteCategory) + categories.Patch("/:categoryID", categoryController.UpdateCategory) + + return categories +} diff --git a/backend/src/server/routes/category_tag.go b/backend/src/server/routes/category_tag.go new file mode 100644 index 000000000..7720e7bb5 --- /dev/null +++ b/backend/src/server/routes/category_tag.go @@ -0,0 +1,16 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func CategoryTag(router fiber.Router, categoryTagService services.CategoryTagServiceInterface) { + categoryTagController := controllers.NewCategoryTagController(categoryTagService) + + categoryTags := router.Group("/:categoryID/tags") + + categoryTags.Get("/", categoryTagController.GetTagsByCategory) + categoryTags.Get("/:tagID", categoryTagController.GetTagByCategory) +} diff --git a/backend/src/server/routes/club.go b/backend/src/server/routes/club.go new file mode 100644 index 000000000..15de716bd --- /dev/null +++ b/backend/src/server/routes/club.go @@ -0,0 +1,35 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/middleware" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/gofiber/fiber/v2" +) + +func Club(router fiber.Router, clubService services.ClubServiceInterface, middlewareService middleware.MiddlewareInterface) fiber.Router { + clubController := controllers.NewClubController(clubService) + + clubs := router.Group("/clubs") + + clubs.Get("/", middlewareService.Authorize(types.ClubReadAll), clubController.GetAllClubs) + clubs.Post("/", clubController.CreateClub) + + // api/v1/clubs/:clubID/* + clubsID := clubs.Group("/:clubID") + clubsID.Use(middleware.SuperSkipper(middlewareService.UserAuthorizeById)) + + clubsID.Get("/", clubController.GetClub) + clubsID.Patch("/", middlewareService.Authorize(types.ClubWrite), clubController.UpdateClub) + clubsID.Delete("/", middlewareService.Authorize(types.ClubDelete), clubController.DeleteClub) + + // api/v1/clubs/:clubID/poc/* + pointOfContact := router.Group("/clubs/:clubID/poc") + pointOfContact.Get("/", clubController.GetAllPointOfContact) + pointOfContact.Get("/:pocID", clubController.GetPointOfContact) + pointOfContact.Put("/", clubController.UpsertPointOfContact) + pointOfContact.Delete("/:pocID", clubController.DeletePointOfContact) + + return clubsID +} diff --git a/backend/src/server/routes/club_contact.go b/backend/src/server/routes/club_contact.go new file mode 100644 index 000000000..4d0055c5d --- /dev/null +++ b/backend/src/server/routes/club_contact.go @@ -0,0 +1,17 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func ClubContact(clubsIDRouter fiber.Router, clubContactService services.ClubContactServiceInterface) { + clubContactController := controllers.NewClubContactController(clubContactService) + + clubContacts := clubsIDRouter.Group("/contacts") + + // api/v1/clubs/:clubID/contacts/* + clubContacts.Get("/", clubContactController.GetClubContacts) + clubContacts.Put("/", clubContactController.PutContact) +} diff --git a/backend/src/server/routes/contact.go b/backend/src/server/routes/contact.go new file mode 100644 index 000000000..a5c7beddb --- /dev/null +++ b/backend/src/server/routes/contact.go @@ -0,0 +1,18 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func Contact(router fiber.Router, contactService services.ContactServiceInterface) { + contactController := controllers.NewContactController(contactService) + + // api/v1/contacts/* + contacts := router.Group("/contacts") + + contacts.Get("/", contactController.GetContacts) + contacts.Get("/:contactID", contactController.GetContact) + contacts.Delete("/:contactID", contactController.DeleteContact) +} diff --git a/backend/src/server/routes/file.go b/backend/src/server/routes/file.go new file mode 100644 index 000000000..4be8ab686 --- /dev/null +++ b/backend/src/server/routes/file.go @@ -0,0 +1,15 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func fileRoutes(router fiber.Router, fileService services.FileServiceInterface) { + fileController := controllers.NewFileController(fileService) + + file := router.Group("/file") + file.Post("/", fileController.CreateFile) +} + diff --git a/backend/src/server/routes/tag.go b/backend/src/server/routes/tag.go new file mode 100644 index 000000000..6bd9bf8b8 --- /dev/null +++ b/backend/src/server/routes/tag.go @@ -0,0 +1,18 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func Tag(router fiber.Router, tagService services.TagServiceInterface) { + tagController := controllers.NewTagController(tagService) + + tags := router.Group("/tags") + + tags.Get("/:tagID", tagController.GetTag) + tags.Post("/", tagController.CreateTag) + tags.Patch("/:tagID", tagController.UpdateTag) + tags.Delete("/:tagID", tagController.DeleteTag) +} diff --git a/backend/src/server/routes/user.go b/backend/src/server/routes/user.go new file mode 100644 index 000000000..2926e875c --- /dev/null +++ b/backend/src/server/routes/user.go @@ -0,0 +1,28 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/middleware" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/GenerateNU/sac/backend/src/types" + "github.com/gofiber/fiber/v2" +) + +func User(router fiber.Router, userService services.UserServiceInterface, middlewareService middleware.MiddlewareInterface) fiber.Router { + userController := controllers.NewUserController(userService) + + // api/v1/users/* + users := router.Group("/users") + users.Post("/", userController.CreateUser) + users.Get("/", middleware.SuperSkipper(middlewareService.Authorize(types.UserReadAll)), userController.GetUsers) + + // api/v1/users/:userID/* + usersID := users.Group("/:userID") + usersID.Use(middleware.SuperSkipper(middlewareService.UserAuthorizeById)) + + usersID.Get("/", userController.GetUser) + usersID.Patch("/", userController.UpdateUser) + usersID.Delete("/", userController.DeleteUser) + + return users +} diff --git a/backend/src/server/routes/user_tag.go b/backend/src/server/routes/user_tag.go new file mode 100644 index 000000000..1b777ff47 --- /dev/null +++ b/backend/src/server/routes/user_tag.go @@ -0,0 +1,16 @@ +package routes + +import ( + "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/services" + "github.com/gofiber/fiber/v2" +) + +func UserTag(router fiber.Router, userTagService services.UserTagServiceInterface) { + userTagController := controllers.NewUserTagController(userTagService) + + userTags := router.Group("/:userID/tags") + + userTags.Post("/", userTagController.CreateUserTags) + userTags.Get("/", userTagController.GetUserTags) +} diff --git a/backend/src/server/routes/utility.go b/backend/src/server/routes/utility.go new file mode 100644 index 000000000..5730a021d --- /dev/null +++ b/backend/src/server/routes/utility.go @@ -0,0 +1,13 @@ +package routes + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/swagger" +) + +func Utility(router fiber.Router) { + router.Get("/swagger/*", swagger.HandlerDefault) + router.Get("/health", func(c *fiber.Ctx) error { + return c.SendStatus(200) + }) +} diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 9446faec0..77bf99d82 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -2,17 +2,15 @@ package server import ( "github.com/GenerateNU/sac/backend/src/config" - "github.com/GenerateNU/sac/backend/src/controllers" + "github.com/GenerateNU/sac/backend/src/middleware" + "github.com/GenerateNU/sac/backend/src/server/routes" "github.com/GenerateNU/sac/backend/src/services" "github.com/GenerateNU/sac/backend/src/utilities" - "github.com/go-playground/validator/v10" "github.com/goccy/go-json" - "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/requestid" - "github.com/gofiber/swagger" "gorm.io/gorm" ) @@ -24,22 +22,36 @@ import ( // @contact.email oduneye.d@northeastern.edu and ladley.g@northeastern.edu // @host 127.0.0.1:8080 // @BasePath / -func Init(db *gorm.DB, config config.Settings) *fiber.App { +func Init(db *gorm.DB, settings config.Settings) *fiber.App { app := newFiberApp() - validate := validator.New(validator.WithRequiredStructEnabled()) - // MARK: Custom validator tags can be registered here. - utilities.RegisterCustomValidators(validate) + validate, err := utilities.RegisterCustomValidators() + if err != nil { + panic(err) + } - utilityRoutes(app) + middlewareService := middleware.NewMiddlewareService(db, validate, settings.Auth) apiv1 := app.Group("/api/v1") + apiv1.Use(middlewareService.Authenticate) + + routes.Utility(app) + + routes.Auth(apiv1, services.NewAuthService(db, validate), settings.Auth) + + userRouter := routes.User(apiv1, services.NewUserService(db, validate), middlewareService) + routes.UserTag(userRouter, services.NewUserTagService(db, validate)) + + routes.Contact(apiv1, services.NewContactService(db, validate)) + + clubsRouter := routes.Club(apiv1, services.NewClubService(db, validate), middlewareService) + routes.ClubContact(clubsRouter, services.NewClubContactService(db, validate)) + + routes.Tag(apiv1, services.NewTagService(db, validate)) + + categoryRouter := routes.Category(apiv1, services.NewCategoryService(db, validate)) + routes.CategoryTag(categoryRouter, services.NewCategoryTagService(db, validate)) - userRoutes(apiv1, &services.UserService{DB: db, Validate: validate}) - categoryRoutes(apiv1, &services.CategoryService{DB: db, Validate: validate}) - tagRoutes(apiv1, &services.TagService{DB: db, Validate: validate}) - clubRoutes(apiv1, &services.ClubService{DB: db, Validate: validate}) - fileRoutes(apiv1, &services.FileService{DB: db, Settings: config.AWS}) return app } @@ -49,7 +61,10 @@ func newFiberApp() *fiber.App { JSONDecoder: json.Unmarshal, }) - app.Use(cors.New()) + app.Use(cors.New(cors.Config{ + AllowOrigins: "*", + AllowCredentials: true, + })) app.Use(requestid.New()) app.Use(logger.New(logger.Config{ Format: "[${time}] ${ip}:${port} ${pid} ${locals:requestid} ${status} - ${latency} ${method} ${path}\n", @@ -57,58 +72,3 @@ func newFiberApp() *fiber.App { return app } - -func utilityRoutes(router fiber.Router) { - router.Get("/swagger/*", swagger.HandlerDefault) - router.Get("/health", func(c *fiber.Ctx) error { - return c.SendStatus(200) - }) -} - -func userRoutes(router fiber.Router, userService services.UserServiceInterface) { - userController := controllers.NewUserController(userService) - - users := router.Group("/users") - - users.Get("/", userController.GetAllUsers) - users.Post("/", userController.CreateUser) - users.Get("/:id", userController.GetUser) - users.Patch("/:id", userController.UpdateUser) - users.Delete("/:id", userController.DeleteUser) -} - -func categoryRoutes(router fiber.Router, categoryService services.CategoryServiceInterface) { - categoryController := controllers.NewCategoryController(categoryService) - - categories := router.Group("/categories") - - categories.Post("/", categoryController.CreateCategory) -} - -func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) { - tagController := controllers.NewTagController(tagService) - - tags := router.Group("/tags") - - tags.Get("/:id", tagController.GetTag) - tags.Post("/", tagController.CreateTag) - tags.Patch("/:id", tagController.UpdateTag) - tags.Delete("/:id", tagController.DeleteTag) -} - -func clubRoutes(router fiber.Router, clubService services.ClubServiceInterface) { - clubController := controllers.NewClubController(clubService) - - pointOfContact := router.Group("/clubs/:id/poc") - pointOfContact.Get("/", clubController.GetAllPointOfContact) - pointOfContact.Get("/:pocId", clubController.GetPointOfContact) - pointOfContact.Put("/", clubController.UpsertPointOfContact) - pointOfContact.Delete("/:pocId", clubController.DeletePointOfContact) -} - -func fileRoutes(router fiber.Router, fileService services.FileServiceInterface) { - fileController := controllers.NewFileController(fileService) - - file := router.Group("/file") - file.Post("/", fileController.CreateFile) -} diff --git a/backend/src/services/auth.go b/backend/src/services/auth.go new file mode 100644 index 000000000..4e0d4de72 --- /dev/null +++ b/backend/src/services/auth.go @@ -0,0 +1,81 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type AuthServiceInterface interface { + GetRole(id string) (*models.UserRole, *errors.Error) + Me(id string) (*models.User, *errors.Error) + Login(userBody models.LoginUserResponseBody) (*models.User, *errors.Error) +} + +type AuthService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewAuthService(db *gorm.DB, validate *validator.Validate) *AuthService { + return &AuthService{ + DB: db, + Validate: validate, + } +} + +func (a *AuthService) Me(id string) (*models.User, *errors.Error) { + idAsUint, idErr := utilities.ValidateID(id) + if idErr != nil { + return nil, idErr + } + + user, err := transactions.GetUser(a.DB, *idAsUint) + if err != nil { + return nil, &errors.UserNotFound + } + + return user, nil +} + +func (a *AuthService) Login(userBody models.LoginUserResponseBody) (*models.User, *errors.Error) { + if err := a.Validate.Struct(userBody); err != nil { + return nil, &errors.FailedToValidateUser + } + + user, err := transactions.GetUserByEmail(a.DB, userBody.Email) + if err != nil { + return nil, &errors.UserNotFound + } + + correct, passwordErr := auth.ComparePasswordAndHash(userBody.Password, user.PasswordHash) + if passwordErr != nil { + return nil, &errors.FailedToValidateUser + } + + if !correct { + return nil, &errors.FailedToValidateUser + } + + return user, nil +} + +func (a *AuthService) GetRole(id string) (*models.UserRole, *errors.Error) { + idAsUint, idErr := utilities.ValidateID(id) + if idErr != nil { + return nil, idErr + } + + user, err := transactions.GetUser(a.DB, *idAsUint) + if err != nil { + return nil, &errors.UserNotFound + } + + role := user.Role + + return &role, nil +} diff --git a/backend/src/services/category.go b/backend/src/services/category.go index dbf8ebfeb..ead4df2c0 100644 --- a/backend/src/services/category.go +++ b/backend/src/services/category.go @@ -11,12 +11,15 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type CategoryServiceInterface interface { CreateCategory(categoryBody models.CategoryRequestBody) (*models.Category, *errors.Error) + GetCategories(limit string, page string) ([]models.Category, *errors.Error) + GetCategory(id string) (*models.Category, *errors.Error) + UpdateCategory(id string, params models.CategoryRequestBody) (*models.Category, *errors.Error) + DeleteCategory(id string) *errors.Error } type CategoryService struct { @@ -24,17 +27,75 @@ type CategoryService struct { Validate *validator.Validate } +func NewCategoryService(db *gorm.DB, validate *validator.Validate) *CategoryService { + return &CategoryService{DB: db, Validate: validate} +} + func (c *CategoryService) CreateCategory(categoryBody models.CategoryRequestBody) (*models.Category, *errors.Error) { if err := c.Validate.Struct(categoryBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateCategory} + return nil, &errors.FailedToValidateCategory } - category, err := utilities.MapResponseToModel(categoryBody, &models.Category{}) + category, err := utilities.MapRequestToModel(categoryBody, &models.Category{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToMapResposeToModel} + return nil, &errors.FailedToMapRequestToModel } category.Name = cases.Title(language.English).String(category.Name) return transactions.CreateCategory(c.DB, *category) } + +func (c *CategoryService) GetCategories(limit string, page string) ([]models.Category, *errors.Error) { + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) + if err != nil { + return nil, &errors.FailedToValidatePage + } + + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetCategories(c.DB, *limitAsInt, offset) +} + +func (c *CategoryService) GetCategory(id string) (*models.Category, *errors.Error) { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + return transactions.GetCategory(c.DB, *idAsUUID) +} + +func (c *CategoryService) UpdateCategory(id string, categoryBody models.CategoryRequestBody) (*models.Category, *errors.Error) { + idAsUUID, idErr := utilities.ValidateID(id) + if idErr != nil { + return nil, idErr + } + + if err := c.Validate.Struct(categoryBody); err != nil { + return nil, &errors.FailedToValidateTag + } + + category, err := utilities.MapRequestToModel(categoryBody, &models.Category{}) + if err != nil { + return nil, &errors.FailedToMapRequestToModel + } + + category.Name = cases.Title(language.English).String(category.Name) + + return transactions.UpdateCategory(c.DB, *idAsUUID, *category) +} + +func (c *CategoryService) DeleteCategory(id string) *errors.Error { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return err + } + + return transactions.DeleteCategory(c.DB, *idAsUUID) +} diff --git a/backend/src/services/category_tag.go b/backend/src/services/category_tag.go new file mode 100644 index 000000000..a16286687 --- /dev/null +++ b/backend/src/services/category_tag.go @@ -0,0 +1,61 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type CategoryTagServiceInterface interface { + GetTagsByCategory(categoryID string, limit string, page string) ([]models.Tag, *errors.Error) + GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) +} + +type CategoryTagService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewCategoryTagService(db *gorm.DB, validate *validator.Validate) *CategoryTagService { + return &CategoryTagService{DB: db, Validate: validate} +} + +func (t *CategoryTagService) GetTagsByCategory(categoryID string, limit string, page string) ([]models.Tag, *errors.Error) { + categoryIDAsUUID, err := utilities.ValidateID(categoryID) + if err != nil { + return nil, err + } + + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) + if err != nil { + return nil, &errors.FailedToValidatePage + } + + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetTagsByCategory(t.DB, *categoryIDAsUUID, *limitAsInt, offset) +} + +func (t *CategoryTagService) GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) { + categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) + + if idErr != nil { + return nil, idErr + } + + tagIDAsUUID, idErr := utilities.ValidateID(tagID) + + if idErr != nil { + return nil, idErr + } + + return transactions.GetTagByCategory(t.DB, *categoryIDAsUUID, *tagIDAsUUID) +} diff --git a/backend/src/services/club.go b/backend/src/services/club.go index 4dc12c380..dd68c848d 100644 --- a/backend/src/services/club.go +++ b/backend/src/services/club.go @@ -6,7 +6,6 @@ import ( "github.com/GenerateNU/sac/backend/src/transactions" "github.com/GenerateNU/sac/backend/src/utilities" "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) @@ -15,6 +14,11 @@ type ClubServiceInterface interface { GetAllPointOfContacts(clubId string) ([]models.PointOfContact, *errors.Error) GetPointOfContact(pocId string, clubId string) (*models.PointOfContact, *errors.Error) DeletePointOfContact(pocId string, clubId string) *errors.Error + GetClubs(limit string, page string) ([]models.Club, *errors.Error) + GetClub(id string) (*models.Club, *errors.Error) + CreateClub(clubBody models.CreateClubRequestBody) (*models.Club, *errors.Error) + UpdateClub(id string, clubBody models.UpdateClubRequestBody) (*models.Club, *errors.Error) + DeleteClub(id string) *errors.Error } type ClubService struct { @@ -25,52 +29,122 @@ type ClubService struct { // Upsert A Point of Contact func (u *ClubService) UpsertPointOfContact(clubId string, pointOfContactBody models.CreatePointOfContactBody) (*models.PointOfContact, *errors.Error) { if err := u.Validate.Struct(pointOfContactBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidatePointOfContact} + return nil, &errors.FailedToValidatePointOfContact } - pointOfContact, err := utilities.MapResponseToModel(pointOfContactBody, &models.PointOfContact{}) + pointOfContact, err := utilities.MapRequestToModel(pointOfContactBody, &models.PointOfContact{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToMapResposeToModel} + return nil, &errors.FailedToMapRequestToModel } - clubIdAsUInt, err := utilities.ValidateID(clubId) - pointOfContact.ClubID = *clubIdAsUInt + clubIdAsUUID, err := utilities.ValidateID(clubId) + pointOfContact.ClubID = *clubIdAsUUID if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateClubId} + return nil, &errors.FailedToValidateClub } - return transactions.UpsertPointOfContact(u.DB, pointOfContact) + return transactions.UpsertPointOfContact(u.DB, pointOfContact) } // Get All Point of Contact func (u *ClubService) GetAllPointOfContacts(clubId string) ([]models.PointOfContact, *errors.Error) { - clubIdAsUint, err := utilities.ValidateID(clubId) + clubIdAsUUID, err := utilities.ValidateID(clubId) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateClubId} + return nil, &errors.FailedToValidateClub } - return transactions.GetAllPointOfContacts(u.DB, *clubIdAsUint) + return transactions.GetAllPointOfContacts(u.DB, *clubIdAsUUID) } // Get A Point of Contact func (u *ClubService) GetPointOfContact(pocId string, clubId string) (*models.PointOfContact, *errors.Error) { - clubIdAsUint, errID := utilities.ValidateID(clubId) + clubIdAsUUID, errID := utilities.ValidateID(clubId) if errID != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateClubId} + return nil, &errors.FailedToValidateClub } - pocIdAsUint, err := utilities.ValidateID(pocId) + pocIdAsUUID, err := utilities.ValidateID(pocId) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidatePointOfContactId} + return nil, &errors.FailedToValidatePointOfContactId } - return transactions.GetPointOfContact(u.DB, *pocIdAsUint, *clubIdAsUint) + return transactions.GetPointOfContact(u.DB, *pocIdAsUUID, *clubIdAsUUID) } - // Delete A Point of Contact func (u *ClubService) DeletePointOfContact(pocId string, clubId string) *errors.Error { - clubIdAsUint, errID := utilities.ValidateID(clubId) + clubIdAsUUID, errID := utilities.ValidateID(clubId) if errID != nil { - return &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateClubId} + return &errors.FailedToValidateClub } - pocIdAsUint, err := utilities.ValidateID(pocId) + pocIdAsUUID, err := utilities.ValidateID(pocId) if err != nil { - return &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidatePointOfContactId} + return &errors.FailedToValidatePointOfContactId } - return transactions.DeletePointOfContact(u.DB, *pocIdAsUint, *clubIdAsUint) -} \ No newline at end of file + return transactions.DeletePointOfContact(u.DB, *pocIdAsUUID, *clubIdAsUUID) +} + + + +func NewClubService(db *gorm.DB, validate *validator.Validate) *ClubService { + return &ClubService{DB: db, Validate: validate} +} + +func (c *ClubService) GetClubs(limit string, page string) ([]models.Club, *errors.Error) { + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) + if err != nil { + return nil, &errors.FailedToValidatePage + } + + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetClubs(c.DB, *limitAsInt, offset) +} + +func (c *ClubService) CreateClub(clubBody models.CreateClubRequestBody) (*models.Club, *errors.Error) { + if err := c.Validate.Struct(clubBody); err != nil { + return nil, &errors.FailedToValidateClub + } + + club, err := utilities.MapRequestToModel(clubBody, &models.Club{}) + if err != nil { + return nil, &errors.FailedToMapRequestToModel + } + + return transactions.CreateClub(c.DB, clubBody.UserID, *club) +} + +func (c *ClubService) GetClub(id string) (*models.Club, *errors.Error) { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, &errors.FailedToValidateID + } + + return transactions.GetClub(c.DB, *idAsUUID) +} + +func (c *ClubService) UpdateClub(id string, clubBody models.UpdateClubRequestBody) (*models.Club, *errors.Error) { + idAsUUID, idErr := utilities.ValidateID(id) + if idErr != nil { + return nil, idErr + } + + if err := c.Validate.Struct(clubBody); err != nil { + return nil, &errors.FailedToValidateClub + } + + club, err := utilities.MapRequestToModel(clubBody, &models.Club{}) + if err != nil { + return nil, &errors.FailedToMapRequestToModel + } + + return transactions.UpdateClub(c.DB, *idAsUUID, *club) +} + +func (c *ClubService) DeleteClub(id string) *errors.Error { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return &errors.FailedToValidateID + } + + return transactions.DeleteClub(c.DB, *idAsUUID) +} diff --git a/backend/src/services/club_contact.go b/backend/src/services/club_contact.go new file mode 100644 index 000000000..b39bf471b --- /dev/null +++ b/backend/src/services/club_contact.go @@ -0,0 +1,53 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type ClubContactServiceInterface interface { + GetClubContacts(clubID string) ([]models.Contact, *errors.Error) + PutClubContact(clubID string, contactBody models.PutContactRequestBody) (*models.Contact, *errors.Error) +} + +type ClubContactService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewClubContactService(db *gorm.DB, validate *validator.Validate) *ClubContactService { + return &ClubContactService{DB: db, Validate: validate} +} + +func (c *ClubContactService) GetClubContacts(clubID string) ([]models.Contact, *errors.Error) { + idAsUUID, err := utilities.ValidateID(clubID) + if err != nil { + return nil, &errors.FailedToValidateID + } + + return transactions.GetClubContacts(c.DB, *idAsUUID) +} + +func (c *ClubContactService) PutClubContact(clubID string, contactBody models.PutContactRequestBody) (*models.Contact, *errors.Error) { + idAsUUID, idErr := utilities.ValidateID(clubID) + if idErr != nil { + return nil, idErr + } + + if err := c.Validate.Struct(contactBody); err != nil { + return nil, &errors.FailedToValidateContact + } + + contact, err := utilities.MapRequestToModel(contactBody, &models.Contact{}) + if err != nil { + return nil, &errors.FailedToMapRequestToModel + } + + contact.ClubID = *idAsUUID + + return transactions.PutClubContact(c.DB, *contact) +} diff --git a/backend/src/services/contact.go b/backend/src/services/contact.go new file mode 100644 index 000000000..1d04ea695 --- /dev/null +++ b/backend/src/services/contact.go @@ -0,0 +1,59 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type ContactServiceInterface interface { + GetContacts(limit string, page string) ([]models.Contact, *errors.Error) + GetContact(contactID string) (*models.Contact, *errors.Error) + DeleteContact(contactID string) *errors.Error +} + +type ContactService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewContactService(db *gorm.DB, validate *validator.Validate) *ContactService { + return &ContactService{DB: db, Validate: validate} +} + +func (c *ContactService) GetContacts(limit string, page string) ([]models.Contact, *errors.Error) { + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) + if err != nil { + return nil, &errors.FailedToValidatePage + } + + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetContacts(c.DB, *limitAsInt, offset) +} + +func (c *ContactService) GetContact(contactID string) (*models.Contact, *errors.Error) { + idAsUUID, err := utilities.ValidateID(contactID) + if err != nil { + return nil, &errors.FailedToValidateID + } + + return transactions.GetContact(c.DB, *idAsUUID) +} + +func (c *ContactService) DeleteContact(contactID string) *errors.Error { + idAsUUID, err := utilities.ValidateID(contactID) + if err != nil { + return &errors.FailedToValidateID + } + + return transactions.DeleteContact(c.DB, *idAsUUID) +} diff --git a/backend/src/services/file.go b/backend/src/services/file.go index bd2263aea..fd47c9122 100644 --- a/backend/src/services/file.go +++ b/backend/src/services/file.go @@ -50,7 +50,7 @@ func (f *FileService) GetFile(id string) (*models.File, *errors.Error) { var file models.File if err := f.DB.First(&file, id).Error; err != nil { - return &models.File{}, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToGetFile} + return &models.File{}, &errors.FailedToGetFile } return &file, nil @@ -82,7 +82,7 @@ func (f *FileService) CreateFile(file models.File, data *multipart.FileHeader, r // Check if the file size is greater than 5 MB if data.Size > 5000000 { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.InvalidFileSize} + return nil, &errors.InvalidFileSize } file.FileSize = data.Size @@ -90,7 +90,7 @@ func (f *FileService) CreateFile(file models.File, data *multipart.FileHeader, r // Upload the file to the S3 bucket sess, err := createAWSSession(f.Settings) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToCreateAWSSession} + return nil, &errors.FailedToCreateAWSSession } uploader := s3manager.NewUploader(sess) @@ -101,13 +101,13 @@ func (f *FileService) CreateFile(file models.File, data *multipart.FileHeader, r Body: reader, }) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUploadToS3} + return nil, &errors.FailedToUploadToS3 } // Create the file in the database if err := f.DB.Create(&file).Error; err != nil { f.DeleteFile(fmt.Sprint(file.ID), true) // delete file from s3 if it cant be made in database - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToCreateFileInDB} + return nil, &errors.FailedToCreateFileInDB } return &file, nil } diff --git a/backend/src/services/tag.go b/backend/src/services/tag.go index 647af2ebf..a7f7523d5 100644 --- a/backend/src/services/tag.go +++ b/backend/src/services/tag.go @@ -6,15 +6,14 @@ import ( "github.com/GenerateNU/sac/backend/src/transactions" "github.com/GenerateNU/sac/backend/src/utilities" "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type TagServiceInterface interface { CreateTag(tagBody models.TagRequestBody) (*models.Tag, *errors.Error) - GetTag(id string) (*models.Tag, *errors.Error) - UpdateTag(id string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) - DeleteTag(id string) *errors.Error + GetTag(tagID string) (*models.Tag, *errors.Error) + UpdateTag(tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) + DeleteTag(tagID string) *errors.Error } type TagService struct { @@ -22,51 +21,58 @@ type TagService struct { Validate *validator.Validate } +func NewTagService(db *gorm.DB, validate *validator.Validate) *TagService { + return &TagService{DB: db, Validate: validate} +} + func (t *TagService) CreateTag(tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { if err := t.Validate.Struct(tagBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateTag} + return nil, &errors.FailedToValidateTag } - tag, err := utilities.MapResponseToModel(tagBody, &models.Tag{}) + tag, err := utilities.MapRequestToModel(tagBody, &models.Tag{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToValidateTag} + return nil, &errors.FailedToMapRequestToModel } return transactions.CreateTag(t.DB, *tag) } -func (t *TagService) GetTag(id string) (*models.Tag, *errors.Error) { - idAsUint, err := utilities.ValidateID(id) - if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} +func (t *TagService) GetTag(tagID string) (*models.Tag, *errors.Error) { + tagIDAsUUID, idErr := utilities.ValidateID(tagID) + + if idErr != nil { + return nil, idErr } - return transactions.GetTag(t.DB, *idAsUint) + return transactions.GetTag(t.DB, *tagIDAsUUID) } -func (t *TagService) UpdateTag(id string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { - idAsUint, err := utilities.ValidateID(id) - if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} +func (t *TagService) UpdateTag(tagID string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) { + tagIDAsUUID, idErr := utilities.ValidateID(tagID) + + if idErr != nil { + return nil, idErr } if err := t.Validate.Struct(tagBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateTag} + return nil, &errors.FailedToValidateTag } - tag, err := utilities.MapResponseToModel(tagBody, &models.Tag{}) + tag, err := utilities.MapRequestToModel(tagBody, &models.Tag{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUpdateTag} + return nil, &errors.FailedToMapRequestToModel } - return transactions.UpdateTag(t.DB, *idAsUint, *tag) + return transactions.UpdateTag(t.DB, *tagIDAsUUID, *tag) } -func (t *TagService) DeleteTag(id string) *errors.Error { - idAsUint, err := utilities.ValidateID(id) - if err != nil { - return &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} +func (t *TagService) DeleteTag(tagID string) *errors.Error { + tagIDAsUUID, idErr := utilities.ValidateID(tagID) + + if idErr != nil { + return idErr } - return transactions.DeleteTag(t.DB, *idAsUint) + return transactions.DeleteTag(t.DB, *tagIDAsUUID) } diff --git a/backend/src/services/user.go b/backend/src/services/user.go index 13360b7a1..a940a6b33 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -10,13 +10,12 @@ import ( "github.com/GenerateNU/sac/backend/src/utilities" "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) type UserServiceInterface interface { - GetAllUsers() ([]models.User, *errors.Error) CreateUser(userBody models.CreateUserRequestBody) (*models.User, *errors.Error) + GetUsers(limit string, page string) ([]models.User, *errors.Error) GetUser(id string) (*models.User, *errors.Error) UpdateUser(id string, userBody models.UpdateUserRequestBody) (*models.User, *errors.Error) DeleteUser(id string) *errors.Error @@ -27,24 +26,23 @@ type UserService struct { Validate *validator.Validate } -// Gets all users (including soft deleted users) for testing -func (u *UserService) GetAllUsers() ([]models.User, *errors.Error) { - return transactions.GetAllUsers(u.DB) +func NewUserService(db *gorm.DB, validate *validator.Validate) *UserService { + return &UserService{DB: db, Validate: validate} } func (u *UserService) CreateUser(userBody models.CreateUserRequestBody) (*models.User, *errors.Error) { if err := u.Validate.Struct(userBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateUser} + return nil, &errors.FailedToValidateUser } - user, err := utilities.MapResponseToModel(userBody, &models.User{}) + user, err := utilities.MapRequestToModel(userBody, &models.User{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToMapResposeToModel} + return nil, &errors.FailedToMapRequestToModel } passwordHash, err := auth.ComputePasswordHash(userBody.Password) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.InternalServerError} + return nil, &errors.FailedToComputePasswordHash } user.Email = strings.ToLower(userBody.Email) @@ -53,46 +51,54 @@ func (u *UserService) CreateUser(userBody models.CreateUserRequestBody) (*models return transactions.CreateUser(u.DB, user) } -func (u *UserService) GetUser(id string) (*models.User, *errors.Error) { - idAsUint, err := utilities.ValidateID(id) +func (u *UserService) GetUsers(limit string, page string) ([]models.User, *errors.Error) { + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} + return nil, &errors.FailedToValidatePage } - return transactions.GetUser(u.DB, *idAsUint) + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetUsers(u.DB, *limitAsInt, offset) } -func (u *UserService) UpdateUser(id string, userBody models.UpdateUserRequestBody) (*models.User, *errors.Error) { - idAsUint, err := utilities.ValidateID(id) +func (u *UserService) GetUser(id string) (*models.User, *errors.Error) { + idAsUUID, err := utilities.ValidateID(id) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} + return nil, &errors.FailedToValidateID } - if err := u.Validate.Struct(userBody); err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateUser} + return transactions.GetUser(u.DB, *idAsUUID) +} + +func (u *UserService) UpdateUser(id string, userBody models.UpdateUserRequestBody) (*models.User, *errors.Error) { + idAsUUID, idErr := utilities.ValidateID(id) + if idErr != nil { + return nil, idErr } - passwordHash, err := auth.ComputePasswordHash(userBody.Password) - if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.InternalServerError} + if err := u.Validate.Struct(userBody); err != nil { + return nil, &errors.FailedToValidateUser } - user, err := utilities.MapResponseToModel(userBody, &models.User{}) + user, err := utilities.MapRequestToModel(userBody, &models.User{}) if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToMapResposeToModel} + return nil, &errors.FailedToMapRequestToModel } - user.PasswordHash = *passwordHash - - return transactions.UpdateUser(u.DB, *idAsUint, *user) + return transactions.UpdateUser(u.DB, *idAsUUID, *user) } -// Delete user with a specific id func (u *UserService) DeleteUser(id string) *errors.Error { - idAsInt, err := utilities.ValidateID(id) + idAsUUID, err := utilities.ValidateID(id) if err != nil { - return &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.FailedToValidateID} + return err } - return transactions.DeleteUser(u.DB, *idAsInt) + return transactions.DeleteUser(u.DB, *idAsUUID) } diff --git a/backend/src/services/user_tag.go b/backend/src/services/user_tag.go new file mode 100644 index 000000000..27f979f9d --- /dev/null +++ b/backend/src/services/user_tag.go @@ -0,0 +1,54 @@ +package services + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/GenerateNU/sac/backend/src/transactions" + "github.com/GenerateNU/sac/backend/src/utilities" + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type UserTagServiceInterface interface { + GetUserTags(id string) ([]models.Tag, *errors.Error) + CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) +} + +type UserTagService struct { + DB *gorm.DB + Validate *validator.Validate +} + +func NewUserTagService(db *gorm.DB, validate *validator.Validate) *UserTagService { + return &UserTagService{DB: db, Validate: validate} +} + +func (u *UserTagService) GetUserTags(id string) ([]models.Tag, *errors.Error) { + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + return transactions.GetUserTags(u.DB, *idAsUUID) +} + +func (u *UserTagService) CreateUserTags(id string, tagIDs models.CreateUserTagsBody) ([]models.Tag, *errors.Error) { + // Validate the id: + idAsUUID, err := utilities.ValidateID(id) + if err != nil { + return nil, err + } + + if err := u.Validate.Struct(tagIDs); err != nil { + return nil, &errors.FailedToValidateUserTags + } + + // Retrieve a list of valid tags from the ids: + tags, err := transactions.GetTagsByIDs(u.DB, tagIDs.Tags) + if err != nil { + return nil, err + } + + // Update the user to reflect the new tags: + return transactions.CreateUserTags(u.DB, *idAsUUID, tags) +} diff --git a/backend/src/transactions/category.go b/backend/src/transactions/category.go index 69b64e2f0..8d5be8392 100644 --- a/backend/src/transactions/category.go +++ b/backend/src/transactions/category.go @@ -4,35 +4,69 @@ import ( stdliberrors "errors" "github.com/GenerateNU/sac/backend/src/errors" + "github.com/google/uuid" "github.com/GenerateNU/sac/backend/src/models" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) func CreateCategory(db *gorm.DB, category models.Category) (*models.Category, *errors.Error) { if err := db.Create(&category).Error; err != nil { if stdliberrors.Is(err, gorm.ErrDuplicatedKey) { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.CategoryAlreadyExists} + return nil, &errors.CategoryAlreadyExists } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToCreateCategory} + return nil, &errors.FailedToCreateCategory } } return &category, nil } -func GetCategory(db *gorm.DB, id uint) (*models.Category, *errors.Error) { +func GetCategories(db *gorm.DB, limit int, offset int) ([]models.Category, *errors.Error) { + var categories []models.Category + + if err := db.Limit(limit).Offset(offset).Find(&categories).Error; err != nil { + return nil, &errors.FailedToGetCategories + } + + return categories, nil +} + +func GetCategory(db *gorm.DB, id uuid.UUID) (*models.Category, *errors.Error) { var category models.Category if err := db.First(&category, id).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.CategoryNotFound} + return nil, &errors.CategoryNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetCategory} + return nil, &errors.FailedToGetCategory } } return &category, nil } + +func UpdateCategory(db *gorm.DB, id uuid.UUID, category models.Category) (*models.Category, *errors.Error) { + if err := db.Model(&models.Category{}).Where("id = ?", id).Updates(category).First(&category, id).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.TagNotFound + } else { + return nil, &errors.FailedToUpdateTag + } + } + + return &category, nil +} + +func DeleteCategory(db *gorm.DB, id uuid.UUID) *errors.Error { + if result := db.Delete(&models.Category{}, id); result.RowsAffected == 0 { + if result.Error == nil { + return &errors.CategoryNotFound + } else { + return &errors.FailedToDeleteCategory + } + } + + return nil +} diff --git a/backend/src/transactions/category_tag.go b/backend/src/transactions/category_tag.go new file mode 100644 index 000000000..f827fb557 --- /dev/null +++ b/backend/src/transactions/category_tag.go @@ -0,0 +1,43 @@ +package transactions + +import ( + stdliberrors "errors" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/google/uuid" + + "github.com/GenerateNU/sac/backend/src/models" + + "gorm.io/gorm" +) + +func GetTagsByCategory(db *gorm.DB, categoryID uuid.UUID, limit int, offset int) ([]models.Tag, *errors.Error) { + var category models.Category + + if err := db.Where("id = ?", categoryID).First(&category).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.CategoryNotFound + } + return nil, &errors.FailedToGetCategory + } + + var tags []models.Tag + if err := db.Where("category_id = ?", categoryID).Limit(limit).Offset(offset).Find(&tags).Error; err != nil { + return nil, &errors.FailedToGetTags + } + + return tags, nil +} + +func GetTagByCategory(db *gorm.DB, categoryID uuid.UUID, tagID uuid.UUID) (*models.Tag, *errors.Error) { + var tag models.Tag + if err := db.Where("category_id = ? AND id = ?", categoryID, tagID).First(&tag).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.TagNotFound + } else { + return nil, &errors.FailedToGetTag + } + } + + return &tag, nil +} diff --git a/backend/src/transactions/club.go b/backend/src/transactions/club.go index c9a9b7ea0..3ba8f17e6 100644 --- a/backend/src/transactions/club.go +++ b/backend/src/transactions/club.go @@ -5,7 +5,7 @@ import ( "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" - "github.com/gofiber/fiber/v2" + "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -15,79 +15,142 @@ func UpsertPointOfContact(db *gorm.DB, pointOfContact *models.PointOfContact) (* err := db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "email"}, {Name: "club_id"}}, DoUpdates: clause.AssignmentColumns([]string{"name", "photo", "email", "position"}), - }).Create(&pointOfContact).Error - if err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUpsertPointOfContact} + }).Create(&pointOfContact).Error + if err != nil { + return nil, &errors.FailedToUpsertPointOfContact } return pointOfContact, nil } // Get All Point of Contacts -func GetAllPointOfContacts(db *gorm.DB, clubId uint) ([]models.PointOfContact, *errors.Error) { +func GetAllPointOfContacts(db *gorm.DB, clubId uuid.UUID) ([]models.PointOfContact, *errors.Error) { var pointOfContacts []models.PointOfContact var club models.Club // handles error when club id is not found if err := db.First(&club, clubId).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.ClubNotFound} + return nil, &errors.FailedToGetAllPointOfContact } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetClub} + return nil, &errors.FailedToGetClub } } else { // club id is found, handle error when failed to get point of contact if err = db.Find(&pointOfContacts).Error; err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetAllPointOfContact} + return nil, &errors.FailedToGetAllPointOfContact } return pointOfContacts, nil } } // Get A Point of Contact -func GetPointOfContact(db *gorm.DB, pocId uint, clubId uint) (*models.PointOfContact, *errors.Error) { +func GetPointOfContact(db *gorm.DB, pocId uuid.UUID, clubId uuid.UUID) (*models.PointOfContact, *errors.Error) { var club models.Club if err := db.First(&club, clubId).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.ClubNotFound} + return nil, &errors.ClubNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetClub} + return nil, &errors.FailedToGetClub } } else { var pointOfContact models.PointOfContact if err := db.First(&pointOfContact, pocId).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.PointOfContactNotFound} + return nil, &errors.PointOfContactNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetAPointOfContact} + return nil, &errors.FailedToGetAPointOfContact } } return &pointOfContact, nil } } - // Delete a point of contact -func DeletePointOfContact(db *gorm.DB, pocId uint, clubId uint) *errors.Error { +func DeletePointOfContact(db *gorm.DB, pocId uuid.UUID, clubId uuid.UUID) *errors.Error { var deletedPointOfContact models.PointOfContact var club models.Club // handles when club id is not found if err := db.First(&club, clubId).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.ClubNotFound} + return &errors.FailedToDeletePointOfContact } else { - return &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetClub} + return &errors.FailedToGetClub } } else { // search for point of contact's email to delete result := db.Where("id = ?", pocId).Delete(&deletedPointOfContact) if result.RowsAffected == 0 { if result.Error == nil { - return &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.PointOfContactNotFound} + return &errors.PointOfContactNotFound } else { - return &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToDeletePointOfContact} + return &errors.FailedToDeletePointOfContact } } return nil } -} \ No newline at end of file +} + +func GetAdminIDs(db *gorm.DB, clubID uuid.UUID) ([]uuid.UUID, *errors.Error) { + var adminIDs []models.Membership + + if err := db.Where("club_id = ? AND membership_type = ?", clubID, models.MembershipTypeAdmin).Find(&adminIDs).Error; err != nil { + return nil, &errors.FailedtoGetAdminIDs + } + + adminUUIDs := make([]uuid.UUID, 0) + for _, adminID := range adminIDs { + adminUUIDs = append(adminUUIDs, adminID.ClubID) + } + + return adminUUIDs, nil +} + +func GetClubs(db *gorm.DB, limit int, offset int) ([]models.Club, *errors.Error) { + var clubs []models.Club + result := db.Limit(limit).Offset(offset).Find(&clubs) + if result.Error != nil { + return nil, &errors.FailedToGetClubs + } + + return clubs, nil +} + +func CreateClub(db *gorm.DB, userId uuid.UUID, club models.Club) (*models.Club, *errors.Error) { + user, err := GetUser(db, userId) + if err != nil { + return nil, &errors.UserNotFound + } + + tx := db.Begin() + + if err := tx.Create(&club).Error; err != nil { + tx.Rollback() + return nil, &errors.FailedToCreateClub + } + + if err := tx.Model(&club).Association("Admin").Append(user); err != nil { + tx.Rollback() + return nil, &errors.FailedToCreateClub + } + + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return nil, &errors.FailedToCreateClub + } + + return &club, nil +} + +func GetClub(db *gorm.DB, id uuid.UUID) (*models.Club, *errors.Error) { + var club models.Club + if err := db.First(&club, id).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.ClubNotFound + } else { + return nil, &errors.FailedToGetClub + } + } + + return &club, nil +} diff --git a/backend/src/transactions/club_contact.go b/backend/src/transactions/club_contact.go new file mode 100644 index 000000000..7cb7afb87 --- /dev/null +++ b/backend/src/transactions/club_contact.go @@ -0,0 +1,45 @@ +package transactions + +import ( + stdliberrors "errors" + "fmt" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func PutClubContact(db *gorm.DB, contact models.Contact) (*models.Contact, *errors.Error) { + err := db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "club_id"}, {Name: "type"}}, + DoUpdates: clause.AssignmentColumns([]string{"content"}), + }).Create(&contact).Error + if err != nil { + fmt.Println(err) + if stdliberrors.Is(err, gorm.ErrRecordNotFound) || stdliberrors.Is(err, gorm.ErrForeignKeyViolated) { + return nil, &errors.ClubNotFound + } else { + return nil, &errors.FailedToPutContact + } + } + return &contact, nil +} + +func GetClubContacts(db *gorm.DB, clubID uuid.UUID) ([]models.Contact, *errors.Error) { + var club models.Club + if err := db.Preload("Contact").First(&club, clubID).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.ClubNotFound + } else { + return nil, &errors.FailedToGetContacts + } + } + + if club.Contact == nil { + return nil, &errors.FailedToGetContacts + } + + return club.Contact, nil +} diff --git a/backend/src/transactions/contacts.go b/backend/src/transactions/contacts.go new file mode 100644 index 000000000..51ac2bb00 --- /dev/null +++ b/backend/src/transactions/contacts.go @@ -0,0 +1,83 @@ +package transactions + +import ( + stdliberrors "errors" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func GetContacts(db *gorm.DB, limit int, offset int) ([]models.Contact, *errors.Error) { + var contacts []models.Contact + result := db.Limit(limit).Offset(offset).Find(&contacts) + if result.Error != nil { + return nil, &errors.FailedToGetContacts + } + + return contacts, nil +} + +func GetContact(db *gorm.DB, id uuid.UUID) (*models.Contact, *errors.Error) { + var contact models.Contact + if err := db.First(&contact, id).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.ContactNotFound + } else { + return nil, &errors.FailedToGetContact + } + } + + return &contact, nil +} + +func DeleteContact(db *gorm.DB, id uuid.UUID) *errors.Error { + if result := db.Delete(&models.Contact{}, id); result.RowsAffected == 0 { + if result.Error == nil { + return &errors.ContactNotFound + } else { + return &errors.FailedToDeleteContact + } + } + return nil +} + +func UpdateClub(db *gorm.DB, id uuid.UUID, club models.Club) (*models.Club, *errors.Error) { + result := db.Model(&models.User{}).Where("id = ?", id).Updates(club) + if result.Error != nil { + if stdliberrors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, &errors.UserNotFound + } else { + return nil, &errors.FailedToUpdateClub + } + } + var existingClub models.Club + + err := db.First(&existingClub, id).Error + if err != nil { + if stdliberrors.Is(err, gorm.ErrRecordNotFound) { + return nil, &errors.ClubNotFound + } else { + return nil, &errors.FailedToCreateClub + } + } + + if err := db.Model(&existingClub).Updates(&club).Error; err != nil { + return nil, &errors.FailedToUpdateUser + } + + return &existingClub, nil +} + +func DeleteClub(db *gorm.DB, id uuid.UUID) *errors.Error { + if result := db.Delete(&models.Club{}, id); result.RowsAffected == 0 { + if result.Error == nil { + return &errors.ClubNotFound + } else { + return &errors.FailedToDeleteClub + } + } + + return nil +} diff --git a/backend/src/transactions/tag.go b/backend/src/transactions/tag.go index 76dd4f1f2..503468f0c 100644 --- a/backend/src/transactions/tag.go +++ b/backend/src/transactions/tag.go @@ -4,56 +4,82 @@ import ( stdliberrors "errors" "github.com/GenerateNU/sac/backend/src/errors" + "github.com/google/uuid" "github.com/GenerateNU/sac/backend/src/models" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) func CreateTag(db *gorm.DB, tag models.Tag) (*models.Tag, *errors.Error) { - if err := db.Create(&tag).Error; err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToCreateTag} + tx := db.Begin() + + var category models.Category + if err := tx.Where("id = ?", tag.CategoryID).First(&category).Error; err != nil { + if err == gorm.ErrRecordNotFound { + tx.Rollback() + return nil, &errors.CategoryNotFound + } else { + tx.Rollback() + return nil, &errors.InternalServerError + } } + if err := tx.Create(&tag).Error; err != nil { + tx.Rollback() + return nil, &errors.FailedToCreateTag + } + + tx.Commit() + return &tag, nil } -func GetTag(db *gorm.DB, id uint) (*models.Tag, *errors.Error) { +func GetTag(db *gorm.DB, tagID uuid.UUID) (*models.Tag, *errors.Error) { var tag models.Tag - - if err := db.First(&tag, id).Error; err != nil { + if err := db.Where("id = ?", tagID).First(&tag).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.TagNotFound} + return nil, &errors.TagNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetTag} + return nil, &errors.FailedToGetTag } } return &tag, nil } -func UpdateTag(db *gorm.DB, id uint, tag models.Tag) (*models.Tag, *errors.Error) { +func UpdateTag(db *gorm.DB, id uuid.UUID, tag models.Tag) (*models.Tag, *errors.Error) { if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).First(&tag, id).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.TagNotFound} + return nil, &errors.TagNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUpdateTag} + return nil, &errors.FailedToUpdateTag } } return &tag, nil - } -func DeleteTag(db *gorm.DB, id uint) *errors.Error { - if result := db.Delete(&models.Tag{}, id); result.RowsAffected == 0 { +func DeleteTag(db *gorm.DB, tagID uuid.UUID) *errors.Error { + if result := db.Where("id = ?", tagID).Delete(&models.Tag{}); result.RowsAffected == 0 { if result.Error == nil { - return &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.TagNotFound} + return &errors.TagNotFound } else { - return &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToDeleteTag} + return &errors.FailedToDeleteTag } } return nil } + +func GetTagsByIDs(db *gorm.DB, selectedTagIDs []uuid.UUID) ([]models.Tag, *errors.Error) { + if len(selectedTagIDs) != 0 { + var tags []models.Tag + if err := db.Model(models.Tag{}).Where("id IN ?", selectedTagIDs).Find(&tags).Error; err != nil { + return nil, &errors.FailedToGetTag + } + + return tags, nil + } + return []models.Tag{}, nil +} diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 23e743b24..14d52e2f5 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -5,74 +5,82 @@ import ( "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) -func GetAllUsers(db *gorm.DB) ([]models.User, *errors.Error) { +func CreateUser(db *gorm.DB, user *models.User) (*models.User, *errors.Error) { + if err := db.Create(user).Error; err != nil { + if stdliberrors.Is(err, gorm.ErrDuplicatedKey) { + return nil, &errors.UserAlreadyExists + } else { + return nil, &errors.FailedToCreateUser + } + } + + return user, nil +} + +func GetUserByEmail(db *gorm.DB, email string) (*models.User, *errors.Error) { + var user models.User + + if err := db.Where("email = ?", email).First(&user).Error; err != nil { + return nil, &errors.UserNotFound + } + + return &user, nil +} + +func GetUsers(db *gorm.DB, limit int, offset int) ([]models.User, *errors.Error) { var users []models.User - if err := db.Omit("password_hash").Find(&users).Error; err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetAllUsers} + if err := db.Omit("password_hash").Limit(limit).Offset(offset).Find(&users).Error; err != nil { + return nil, &errors.FailedToGetUsers } return users, nil } -func GetUser(db *gorm.DB, id uint) (*models.User, *errors.Error) { +func GetUser(db *gorm.DB, id uuid.UUID) (*models.User, *errors.Error) { var user models.User if err := db.Omit("password_hash").First(&user, id).Error; err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.UserNotFound} + return nil, &errors.UserNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetUser} + return nil, &errors.FailedToGetUser } } return &user, nil } -func CreateUser(db *gorm.DB, user *models.User) (*models.User, *errors.Error) { - if err := db.Create(user).Error; err != nil { - if stdliberrors.Is(err, gorm.ErrDuplicatedKey) { - return nil, &errors.Error{StatusCode: fiber.StatusBadRequest, Message: errors.UserAlreadyExists} - } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToCreateUser} - } - } - - return user, nil -} - -func UpdateUser(db *gorm.DB, id uint, user models.User) (*models.User, *errors.Error) { +func UpdateUser(db *gorm.DB, id uuid.UUID, user models.User) (*models.User, *errors.Error) { var existingUser models.User err := db.First(&existingUser, id).Error if err != nil { if stdliberrors.Is(err, gorm.ErrRecordNotFound) { - return nil, &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.UserNotFound} + return nil, &errors.UserNotFound } else { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToGetUser} + return nil, &errors.FailedToUpdateTag } } if err := db.Model(&existingUser).Updates(&user).Error; err != nil { - return nil, &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToUpdateUser} + return nil, &errors.FailedToUpdateUser } return &existingUser, nil } -func DeleteUser(db *gorm.DB, id uint) *errors.Error { - var deletedUser models.User - - result := db.Where("id = ?", id).Delete(&deletedUser) +func DeleteUser(db *gorm.DB, id uuid.UUID) *errors.Error { + result := db.Delete(&models.User{}, id) if result.RowsAffected == 0 { if result.Error == nil { - return &errors.Error{StatusCode: fiber.StatusNotFound, Message: errors.UserNotFound} + return &errors.UserNotFound } else { - return &errors.Error{StatusCode: fiber.StatusInternalServerError, Message: errors.FailedToDeleteUser} + return &errors.FailedToDeleteUser } } return nil diff --git a/backend/src/transactions/user_tag.go b/backend/src/transactions/user_tag.go new file mode 100644 index 000000000..a8c1dd01d --- /dev/null +++ b/backend/src/transactions/user_tag.go @@ -0,0 +1,35 @@ +package transactions + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func GetUserTags(db *gorm.DB, id uuid.UUID) ([]models.Tag, *errors.Error) { + var tags []models.Tag + + user, err := GetUser(db, id) + if err != nil { + return nil, &errors.UserNotFound + } + + if err := db.Model(&user).Association("Tag").Find(&tags); err != nil { + return nil, &errors.FailedToGetTag + } + return tags, nil +} + +func CreateUserTags(db *gorm.DB, id uuid.UUID, tags []models.Tag) ([]models.Tag, *errors.Error) { + user, err := GetUser(db, id) + if err != nil { + return nil, &errors.UserNotFound + } + + if err := db.Model(&user).Association("Tag").Replace(tags); err != nil { + return nil, &errors.FailedToUpdateUser + } + + return tags, nil +} diff --git a/backend/src/types/custom_claims.go b/backend/src/types/custom_claims.go new file mode 100644 index 000000000..c0351e0a0 --- /dev/null +++ b/backend/src/types/custom_claims.go @@ -0,0 +1,26 @@ +package types + +import ( + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt" +) + +type CustomClaims struct { + jwt.StandardClaims + Role string `json:"role"` +} + +func From(c *fiber.Ctx) (*CustomClaims, *errors.Error) { + rawClaims := c.Locals("claims") + if rawClaims == nil { + return nil, nil + } + + claims, ok := rawClaims.(*CustomClaims) + if !ok { + return nil, &errors.FailedToCastToCustomClaims + } + + return claims, nil +} diff --git a/backend/src/types/permissions.go b/backend/src/types/permissions.go new file mode 100644 index 000000000..8361f42eb --- /dev/null +++ b/backend/src/types/permissions.go @@ -0,0 +1,91 @@ +package types + +import "github.com/GenerateNU/sac/backend/src/models" + +type Permission string + +const ( + UserReadAll Permission = "user:readAll" + UserRead Permission = "user:read" + UserWrite Permission = "user:write" + UserDelete Permission = "user:delete" + + TagReadAll Permission = "tag:readAll" + TagRead Permission = "tag:read" + TagWrite Permission = "tag:write" + TagCreate Permission = "tag:create" + TagDelete Permission = "tag:delete" + + ClubReadAll Permission = "club:readAll" + ClubRead Permission = "club:read" + ClubWrite Permission = "club:write" + ClubCreate Permission = "club:create" + ClubDelete Permission = "club:delete" + + PointOfContactReadAll Permission = "pointOfContact:readAll" + PointOfContactRead Permission = "pointOfContact:read" + PointOfContactCreate Permission = "pointOfContact:create" + PointOfContactWrite Permission = "pointOfContact:write" + PointOfContactDelete Permission = "pointOfContact:delete" + + CommentReadAll Permission = "comment:readAll" + CommentRead Permission = "comment:read" + CommentCreate Permission = "comment:create" + CommentWrite Permission = "comment:write" + CommentDelete Permission = "comment:delete" + + EventReadAll Permission = "event:readAll" + EventRead Permission = "event:read" + EventWrite Permission = "event:write" + EventCreate Permission = "event:create" + EventDelete Permission = "event:delete" + + ContactReadAll Permission = "contact:readAll" + ContactRead Permission = "contact:read" + ContactWrite Permission = "contact:write" + ContactCreate Permission = "contact:create" + ContactDelete Permission = "contact:delete" + + CategoryReadAll Permission = "category:readAll" + CategoryRead Permission = "category:read" + CategoryWrite Permission = "category:write" + CategoryCreate Permission = "category:create" + CategoryDelete Permission = "category:delete" + + NotificationReadAll Permission = "notification:readAll" + NotificationRead Permission = "notification:read" + NotificationWrite Permission = "notification:write" + NotificationCreate Permission = "notification:create" + NotificationDelete Permission = "notification:delete" +) + +var rolePermissions = map[models.UserRole][]Permission{ + models.Super: { + UserRead, UserReadAll, UserWrite, UserDelete, + TagRead, TagCreate, TagWrite, TagDelete, + ClubRead, ClubCreate, ClubWrite, ClubDelete, + PointOfContactRead, PointOfContactCreate, PointOfContactWrite, PointOfContactDelete, + CommentRead, CommentCreate, CommentWrite, CommentDelete, + EventRead, EventCreate, EventWrite, EventDelete, + ContactRead, ContactCreate, ContactWrite, ContactDelete, + CategoryRead, CategoryCreate, CategoryWrite, CategoryDelete, + NotificationRead, NotificationCreate, NotificationWrite, NotificationDelete, + UserReadAll, TagReadAll, ClubReadAll, PointOfContactReadAll, CommentReadAll, EventReadAll, ContactReadAll, CategoryReadAll, NotificationReadAll, + }, + models.Student: { + UserRead, + TagRead, + ClubRead, + PointOfContactRead, + CommentRead, + EventRead, + ContactRead, + CategoryRead, + NotificationRead, + }, +} + +// Returns the permissions for a given role +func GetPermissions(role models.UserRole) []Permission { + return rolePermissions[role] +} diff --git a/backend/src/utilities/contains.go b/backend/src/utilities/contains.go deleted file mode 100644 index f640b232c..000000000 --- a/backend/src/utilities/contains.go +++ /dev/null @@ -1,10 +0,0 @@ -package utilities - -func Contains[T comparable](slice []T, element T) bool { - for _, v := range slice { - if v == element { - return true - } - } - return false -} diff --git a/backend/src/utilities/manipulator.go b/backend/src/utilities/manipulator.go index bada3a068..ff0a1d791 100644 --- a/backend/src/utilities/manipulator.go +++ b/backend/src/utilities/manipulator.go @@ -4,8 +4,8 @@ import ( "github.com/mitchellh/mapstructure" ) -// MapResponseToModel maps response data to a target model using mapstructure -func MapResponseToModel[T any, U any](responseData T, targetModel *U) (*U, error) { +// MapRequestToModel maps request data to a target model using mapstructure +func MapRequestToModel[T any, U any](responseData T, targetModel *U) (*U, error) { config := &mapstructure.DecoderConfig{ Result: targetModel, TagName: "json", @@ -22,4 +22,4 @@ func MapResponseToModel[T any, U any](responseData T, targetModel *U) (*U, error } return targetModel, nil -} \ No newline at end of file +} diff --git a/backend/src/utilities/response.go b/backend/src/utilities/response.go new file mode 100644 index 000000000..790993135 --- /dev/null +++ b/backend/src/utilities/response.go @@ -0,0 +1,7 @@ +package utilities + +import "github.com/gofiber/fiber/v2" + +func FiberMessage(c *fiber.Ctx, statusCode int, response string) error { + return c.Status(statusCode).JSON(fiber.Map{"message": response}) +} diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go index b9058b9bf..3529839cb 100644 --- a/backend/src/utilities/validator.go +++ b/backend/src/utilities/validator.go @@ -4,18 +4,44 @@ import ( "regexp" "strconv" - "github.com/gofiber/fiber/v2" + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" - "github.com/go-playground/validator/v10" + "github.com/google/uuid" "github.com/mcnijman/go-emailaddress" + + "github.com/go-playground/validator/v10" ) -func RegisterCustomValidators(validate *validator.Validate) { - validate.RegisterValidation("neu_email", ValidateEmail) - validate.RegisterValidation("password", ValidatePassword) +func RegisterCustomValidators() (*validator.Validate, error) { + validate := validator.New(validator.WithRequiredStructEnabled()) + + if err := validate.RegisterValidation("neu_email", validateEmail); err != nil { + return nil, err + } + + if err := validate.RegisterValidation("password", validatePassword); err != nil { + return nil, err + } + + if err := validate.RegisterValidation("mongo_url", validateMongoURL); err != nil { + return nil, err + } + + if err := validate.RegisterValidation("s3_url", validateS3URL); err != nil { + return nil, err + } + + if err := validate.RegisterValidation("contact_pointer", func(fl validator.FieldLevel) bool { + return validateContactPointer(validate, fl) + }); err != nil { + return nil, err + } + + return validate, nil } -func ValidateEmail(fl validator.FieldLevel) bool { +func validateEmail(fl validator.FieldLevel) bool { email, err := emailaddress.Parse(fl.Field().String()) if err != nil { return false @@ -28,13 +54,7 @@ func ValidateEmail(fl validator.FieldLevel) bool { return true } -func ValidateGenericEmail(email string) bool { - _, err := emailaddress.Parse(email) - return err == nil -} - - -func ValidatePassword(fl validator.FieldLevel) bool { +func validatePassword(fl validator.FieldLevel) bool { if len(fl.Field().String()) < 8 { return false } @@ -43,21 +63,42 @@ func ValidatePassword(fl validator.FieldLevel) bool { return specialCharactersMatch && numbersMatch } -// Validates that an id follows postgres uint format, returns a uint otherwise returns an error -func ValidateID(id string) (*uint, error) { - idAsInt, err := strconv.Atoi(id) +func validateMongoURL(fl validator.FieldLevel) bool { + return true +} - errMsg := "failed to validate id" +func validateS3URL(fl validator.FieldLevel) bool { + return true +} - if err != nil { - return nil, fiber.NewError(fiber.StatusBadRequest, errMsg) +func validateContactPointer(validate *validator.Validate, fl validator.FieldLevel) bool { + contact, ok := fl.Parent().Interface().(models.PutContactRequestBody) + if !ok { + return false } + switch contact.Type { + case models.Email: + return validate.Var(contact.Content, "email") == nil + default: + return validate.Var(contact.Content, "http_url") == nil + } +} - if idAsInt < 1 { // postgres ids start at 1 - return nil, fiber.NewError(fiber.StatusBadRequest, errMsg) +func ValidateID(id string) (*uuid.UUID, *errors.Error) { + idAsUUID, err := uuid.Parse(id) + if err != nil { + return nil, &errors.FailedToValidateID } - idAsUint := uint(idAsInt) + return &idAsUUID, nil +} + +func ValidateNonNegative(value string) (*int, *errors.Error) { + valueAsInt, err := strconv.Atoi(value) + + if err != nil || valueAsInt < 0 { + return nil, &errors.FailedToValidateNonNegativeValue + } - return &idAsUint, nil + return &valueAsInt, nil } diff --git a/backend/tests/README.md b/backend/tests/README.md index 317eb2800..e9dc17a0a 100644 --- a/backend/tests/README.md +++ b/backend/tests/README.md @@ -21,11 +21,11 @@ Here is an example of creating a `TestRequest`, notice how instead of saying `He ```go TestRequest{ - Method: "POST", + Method: fiber.MethodPost, Path: "/api/v1/tags/", Body: &map[string]interface{}{ "name": tagName, - "category_id": 1, + "category_id": uuid.New(), }, } ``` @@ -38,9 +38,9 @@ Say you want to test hitting the `[APP_ADDRESS]/health` endpoint with a GET requ ```go TestRequest{ - Method: "GET", + Method: fiber.MethodGet, Path: "/health", - }.TestOnStatus(t, nil, 200).Close() + }.TestOnStatus(t, nil, fiber.StatusOK).Close() ``` ## Testing that a Request Returns a XXX Status Code and Assert Something About the Database @@ -49,36 +49,43 @@ Say you want to test that a creating a catgory with POST `[APP_ADDRESS]/api/v1/c ```go TestRequest{ - Method: "POST", + Method: fiber.MethodPost, Path: "/api/v1/categories/", - Body: &map[string]interface{}{ - "category_name": categoryName, - }, - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertRespCategorySameAsDBCategory, + Body: SampleCategoryFactory(), + }.TestOnStatusAndTester(t, nil, + TesterWithStatus{ + Status: fiber.StatusCreated, + DBTester: AssertSampleCategoryBodyRespDB, }, ).Close() ``` ### DBTesters -Often times there are common assertions you want to make about the database, for example, if the object in the response is the same as the object in the database. We can create a lambda function that takes in the `TestApp`, `*assert.A`, and `*http.Response` and makes the assertions we want. We can then pass this function to the `DBTesterWithStatus` struct. +Often times there are common assertions you want to make about the database, for example, if the object in the response is the same as the object in the database. We can create a lambda function that takes in the `TestApp`, `*assert.A`, and `*http.Response` and makes the assertions we want. We can then pass this function to the `TesterWithStatus` struct. ```go -var AssertRespCategorySameAsDBCategory = func(app TestApp, assert *assert.A, resp *http.Response) { +func AssertSampleCategoryBodyRespDB(app TestApp, assert *assert.A, resp *http.Response) { + AssertCategoryWithIDBodyRespDB(app, assert, resp, 1, SampleCategoryFactory()) +} + +func AssertCategoryWithIDBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, id uint, body *map[string]interface{}) { var respCategory models.Category err := json.NewDecoder(resp.Body).Decode(&respCategory) assert.NilError(err) - dbCategory, err := transactions.GetCategory(app.Conn, respCategory.ID) + var dbCategory models.Category + + err = app.Conn.First(&dbCategory, id).Error assert.NilError(err) - assert.Equal(dbCategory, &respCategory) + assert.Equal(dbCategory.ID, respCategory.ID) + assert.Equal(dbCategory.Name, respCategory.Name) + + assert.Equal((*body)["name"].(string), dbCategory.Name) } ``` @@ -86,58 +93,24 @@ var AssertRespCategorySameAsDBCategory = func(app TestApp, assert *assert.A, res Since the test suite creates a new database for each test, we can have a deterministic database state for each test. However, what if we have a multi step test that depends on the previous steps database state? That is where `ExistingAppAssert` comes in! This will allow us to keep using the database from a previous step in the test. -Consider this example, to create a tag, we need to create a category first. This is a multi step test, so we need to use `ExistingAppAssert` to keep the database state from the previous step. - -```go -appAssert := TestRequest{ - Method: "POST", - Path: "/api/v1/categories/", - Body: &map[string]interface{}{ - "category_name": categoryName, - }, - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertRespCategorySameAsDBCategory, - }, - ) - -TestRequest{ - Method: "POST", - Path: "/api/v1/tags/", - Body: &map[string]interface{}{ - "name": tagName, - "category_id": 1, - }, - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertRespTagSameAsDBTag, - }, - ).Close() -``` - ### Why Close? This closes the connection to the database. This is important because if you don't close the connection, we will run out of available connections and the tests will fail. **Call this on the last test request of a test** -## Testing that a Request Returns a XXX Status Code, Assert Something About the Message, and Assert Something About the Database +## Testing that a Request Returns the Correct Error (Status Code and Message), and Assert Something About the Database -Say you want to test a bad request to POST `[APP_ADDRESS]/api/v1/categories/` endpoint returns a `400` status code, the message is `failed to process the request`, and that a category was not created. +Say you want to test a bad request to POST `[APP_ADDRESS]/api/v1/categories/` endpoint returns a `400` status code, the message is `failed to process the request`, and that a category was not created. We can leverage our errors defined in the error package to do this! ```go -TestRequest{ - Method: "POST", + TestRequest{ + Method: fiber.MethodPost, Path: "/api/v1/categories/", Body: &map[string]interface{}{ - "category_name": 1231, + "name": 1231, }, }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: "failed to process the request", - }, + ErrorWithDBTester{ + Error: errors.FailedToParseRequestBody, DBTester: AssertNoCategories, }, ).Close() diff --git a/backend/tests/api/category_tag_test.go b/backend/tests/api/category_tag_test.go new file mode 100644 index 000000000..888c28147 --- /dev/null +++ b/backend/tests/api/category_tag_test.go @@ -0,0 +1,203 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +func AssertTagsWithBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *[]map[string]interface{}) []uuid.UUID { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + var dbTags []models.Tag + + err = eaa.App.Conn.Find(&dbTags).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(dbTags), len(respTags)) + + for i, dbTag := range dbTags { + eaa.Assert.Equal(dbTag.ID, respTags[i].ID) + eaa.Assert.Equal(dbTag.Name, respTags[i].Name) + eaa.Assert.Equal(dbTag.CategoryID, respTags[i].CategoryID) + } + + tagIDs := make([]uuid.UUID, len(dbTags)) + for i, dbTag := range dbTags { + tagIDs[i] = dbTag.ID + } + + return tagIDs +} + +func TestGetCategoryTagsWorks(t *testing.T) { + appAssert, categoryUUID, tagID := CreateSampleTag(h.InitTest(t)) + + body := SampleTagFactory(categoryUUID) + (*body)["id"] = tagID + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", categoryUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertTagsWithBodyRespDB(eaa, resp, &[]map[string]interface{}{*body}) + }, + }, + ).Close() +} + +func TestGetCategoryTagsFailsCategoryBadRequest(t *testing.T) { + appAssert, _ := CreateSampleCategory(h.InitTest(t)) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", badRequest), + Role: &models.Super, + }, + errors.FailedToValidateID, + ) + } + + appAssert.Close() +} + +func TestGetCategoryTagsFailsCategoryNotFound(t *testing.T) { + appAssert, _ := CreateSampleCategory(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", uuid), + Role: &models.Super, + }, h.ErrorWithTester{ + Error: errors.CategoryNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var category models.Category + err := eaa.App.Conn.Where("id = ?", uuid).First(&category).Error + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestGetCategoryTagWorks(t *testing.T) { + existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertTagWithBodyRespDB(eaa, resp, SampleTagFactory(categoryUUID)) + }, + }, + ).Close() +} + +func TestGetCategoryTagFailsCategoryBadRequest(t *testing.T) { + appAssert, _, tagUUID := CreateSampleTag(h.InitTest(t)) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), + Role: &models.Super, + }, errors.FailedToValidateID, + ) + } + + appAssert.Close() +} + +func TestGetCategoryTagFailsTagBadRequest(t *testing.T) { + appAssert, categoryUUID := CreateSampleCategory(h.InitTest(t)) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), + Role: &models.Super, + }, + errors.FailedToValidateID) + } + + appAssert.Close() +} + +func TestGetCategoryTagFailsCategoryNotFound(t *testing.T) { + appAssert, _, tagUUID := CreateSampleTag(h.InitTest(t)) + + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", uuid.New(), tagUUID), + Role: &models.Super, + }, + errors.TagNotFound, + ).Close() +} + +func TestGetCategoryTagFailsTagNotFound(t *testing.T) { + appAssert, categoryUUID := CreateSampleCategory(h.InitTest(t)) + + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, uuid.New()), + Role: &models.Super, + }, + errors.TagNotFound, + ).Close() +} diff --git a/backend/tests/api/category_test.go b/backend/tests/api/category_test.go index fcec9c8a4..921adf4bf 100644 --- a/backend/tests/api/category_test.go +++ b/backend/tests/api/category_test.go @@ -1,154 +1,445 @@ package tests import ( + "fmt" "net/http" "testing" "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" "github.com/gofiber/fiber/v2" - "github.com/huandu/go-assert" + "github.com/google/uuid" "github.com/goccy/go-json" ) func SampleCategoryFactory() *map[string]interface{} { return &map[string]interface{}{ - "category_name": "Foo", + "name": "Foo", } } -func AssertCategoryWithIDBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, id uint, body *map[string]interface{}) { +func AssertCategoryBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { var respCategory models.Category err := json.NewDecoder(resp.Body).Decode(&respCategory) - assert.NilError(err) + eaa.Assert.NilError(err) + + var dbCategories []models.Category + + err = eaa.App.Conn.Find(&dbCategories).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbCategories)) + + dbCategory := dbCategories[0] + + eaa.Assert.Equal(dbCategory.ID, respCategory.ID) + eaa.Assert.Equal(dbCategory.Name, respCategory.Name) + + eaa.Assert.Equal((*body)["name"].(string), dbCategory.Name) + + return dbCategory.ID +} + +func AssertCategoryWithBodyRespDBMostRecent(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { + var respCategory models.Category + + err := json.NewDecoder(resp.Body).Decode(&respCategory) + + eaa.Assert.NilError(err) var dbCategory models.Category - err = app.Conn.First(&dbCategory, id).Error + err = eaa.App.Conn.Order("created_at desc").First(&dbCategory).Error - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(dbCategory.ID, respCategory.ID) - assert.Equal(dbCategory.Name, respCategory.Name) + eaa.Assert.Equal(dbCategory.ID, respCategory.ID) + eaa.Assert.Equal(dbCategory.Name, respCategory.Name) - assert.Equal((*body)["category_name"].(string), dbCategory.Name) + eaa.Assert.Equal((*body)["name"].(string), dbCategory.Name) + return dbCategory.ID } -func AssertSampleCategoryBodyRespDB(app TestApp, assert *assert.A, resp *http.Response) { - AssertCategoryWithIDBodyRespDB(app, assert, resp, 1, SampleCategoryFactory()) +func AssertSampleCategoryBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response) uuid.UUID { + return AssertCategoryBodyRespDB(eaa, resp, SampleCategoryFactory()) } -func CreateSampleCategory(t *testing.T) ExistingAppAssert { - return TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: SampleCategoryFactory(), - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertSampleCategoryBodyRespDB, +func CreateSampleCategory(existingAppAssert h.ExistingAppAssert) (h.ExistingAppAssert, uuid.UUID) { + var sampleCategoryUUID uuid.UUID + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: SampleCategoryFactory(), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + sampleCategoryUUID = AssertSampleCategoryBodyRespDB(eaa, resp) + }, }, ) + + return existingAppAssert, sampleCategoryUUID } func TestCreateCategoryWorks(t *testing.T) { - CreateSampleCategory(t).Close() + existingAppAssert, _ := CreateSampleCategory(h.InitTest(t)) + existingAppAssert.Close() } func TestCreateCategoryIgnoresid(t *testing.T) { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: &map[string]interface{}{ - "id": 12, - "category_name": "Foo", - }, - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertSampleCategoryBodyRespDB, + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &map[string]interface{}{ + "id": 12, + "name": "Foo", + }, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleCategoryBodyRespDB(eaa, resp) + }, }, ).Close() } -func AssertNoCategories(app TestApp, assert *assert.A, resp *http.Response) { - AssertNumCategoriesRemainsAtN(app, assert, resp, 0) +func Assert1Category(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumCategoriesRemainsAtN(eaa, resp, 1) } -func AssertNumCategoriesRemainsAtN(app TestApp, assert *assert.A, resp *http.Response, n int) { +func AssertNoCategories(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumCategoriesRemainsAtN(eaa, resp, 0) +} + +func AssertNumCategoriesRemainsAtN(eaa h.ExistingAppAssert, resp *http.Response, n int) { var categories []models.Category - err := app.Conn.Find(&categories).Error + err := eaa.App.Conn.Find(&categories).Error - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(n, len(categories)) + eaa.Assert.Equal(n, len(categories)) } func TestCreateCategoryFailsIfNameIsNotString(t *testing.T) { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: &map[string]interface{}{ - "category_name": 1231, - }, - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToParseRequestBody, + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &map[string]interface{}{ + "name": 1231, }, - DBTester: AssertNoCategories, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToParseRequestBody, + Tester: AssertNoCategories, }, ).Close() } func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: &map[string]interface{}{}, - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: "failed to validate category", - }, - DBTester: AssertNoCategories, + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &map[string]interface{}{}, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateCategory, + Tester: AssertNoCategories, }, ).Close() } func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) { + existingAppAssert, _ := CreateSampleCategory(h.InitTest(t)) - existingAppAssert := CreateSampleCategory(t) - - var TestNumCategoriesRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertNumCategoriesRemainsAtN(app, assert, resp, 1) + TestNumCategoriesRemainsAt1 := func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumCategoriesRemainsAtN(eaa, resp, 1) } - for _, permutation := range AllCasingPermutations((*SampleCategoryFactory())["category_name"].(string)) { + for _, permutation := range h.AllCasingPermutations((*SampleCategoryFactory())["name"].(string)) { modifiedSampleCategoryBody := *SampleCategoryFactory() - modifiedSampleCategoryBody["category_name"] = permutation + modifiedSampleCategoryBody["name"] = permutation - TestRequest{ - Method: fiber.MethodPost, + existingAppAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &modifiedSampleCategoryBody, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.CategoryAlreadyExists, + Tester: TestNumCategoriesRemainsAt1, + }, + ) + } + + existingAppAssert.Close() +} + +func TestGetCategoryWorks(t *testing.T) { + existingAppAssert, uuid := CreateSampleCategory(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleCategoryBodyRespDB(eaa, resp) + }, + }, + ).Close() +} + +func TestGetCategoryFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), + Role: &models.Super, + }, + errors.FailedToValidateID, + ) + } + + appAssert.Close() +} + +func TestGetCategoryFailsNotFound(t *testing.T) { + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid.New()), + Role: &models.Super, + }, errors.CategoryNotFound, + ).Close() +} + +func TestGetCategoriesWorks(t *testing.T) { + existingAppAssert, _ := CreateSampleCategory(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, Path: "/api/v1/categories/", - Body: &modifiedSampleCategoryBody, - }.TestOnStatusMessageAndDB(t, &existingAppAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.CategoryAlreadyExists, - }, - DBTester: TestNumCategoriesRemainsAt1, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var categories []models.Category + + err := eaa.App.Conn.Find(&categories).Error + + eaa.Assert.NilError(err) + + var respCategories []models.Category + + err = json.NewDecoder(resp.Body).Decode(&respCategories) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(respCategories)) + eaa.Assert.Equal(1, len(categories)) + + eaa.Assert.Equal(categories[0].ID, respCategories[0].ID) + + eaa.Assert.Equal(categories[0].Name, respCategories[0].Name) + + eaa.Assert.Equal((*SampleCategoryFactory())["name"].(string), categories[0].Name) + }, + }, + ).Close() +} + +func AssertUpdatedCategoryBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) { + var respCategory models.Category + + err := json.NewDecoder(resp.Body).Decode(&respCategory) + + eaa.Assert.NilError(err) + + var dbCategories []models.Category + + err = eaa.App.Conn.Find(&dbCategories).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbCategories)) + + dbCategory := dbCategories[0] + + eaa.Assert.Equal(dbCategory.ID, respCategory.ID) + eaa.Assert.Equal(dbCategory.Name, respCategory.Name) + + eaa.Assert.Equal((*body)["id"].(uuid.UUID), dbCategory.ID) + eaa.Assert.Equal((*body)["name"].(string), dbCategory.Name) +} + +func TestUpdateCategoryWorks(t *testing.T) { + existingAppAssert, uuid := CreateSampleCategory(h.InitTest(t)) + + category := map[string]interface{}{ + "id": uuid, + "name": "Arts & Crafts", + } + + AssertUpdatedCategoryBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertUpdatedCategoryBodyRespDB(eaa, resp, &category) + } + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid), + Body: &category, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: AssertUpdatedCategoryBodyRespDB, + }, + ).Close() +} + +func TestUpdateCategoryWorksWithSameDetails(t *testing.T) { + existingAppAssert, uuid := CreateSampleCategory(h.InitTest(t)) + + category := *SampleCategoryFactory() + category["id"] = uuid + + AssertSampleCategoryBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertUpdatedCategoryBodyRespDB(eaa, resp, &category) + } + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid), + Body: &category, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: AssertSampleCategoryBodyRespDB, + }, + ).Close() +} + +func TestUpdateCategoryFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), + Body: SampleCategoryFactory(), + Role: &models.Super, + }, + errors.FailedToValidateID, + ) + } + + appAssert.Close() +} + +func TestDeleteCategoryWorks(t *testing.T) { + existingAppAssert, uuid := CreateSampleCategory(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: AssertNoCategories, + }, + ).Close() +} + +func TestDeleteCategoryFailsBadRequest(t *testing.T) { + existingAppAssert, _ := CreateSampleCategory(h.InitTest(t)) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + existingAppAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/categories/%s", badRequest), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateID, + Tester: Assert1Category, }, ) } existingAppAssert.Close() } + +func TestDeleteCategoryFailsNotFound(t *testing.T) { + existingAppAssert, _ := CreateSampleCategory(h.InitTest(t)) + + existingAppAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/categories/%s", uuid.New()), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.CategoryNotFound, + Tester: Assert1Category, + }, + ).Close() +} diff --git a/backend/tests/api/club_contact_test.go b/backend/tests/api/club_contact_test.go new file mode 100644 index 000000000..ead3052bb --- /dev/null +++ b/backend/tests/api/club_contact_test.go @@ -0,0 +1,138 @@ +package tests + +import ( + stdliberrors "errors" + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func AssertCreateBadContactDataFails(t *testing.T, jsonKey string, badValues []interface{}) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + for _, badValue := range badValues { + sampleContactPermutation := *SampleContactFactory() + sampleContactPermutation[jsonKey] = badValue + + appAssert = appAssert.TestOnErrorAndTester(h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID), + Body: &sampleContactPermutation, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateContact, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumContactsRemainsAtN(eaa, resp, 0) + }, + }, + ) + } + appAssert.Close() +} + +func TestCreateContactFailsOnInvalidType(t *testing.T) { + AssertCreateBadContactDataFails(t, + "type", + []interface{}{ + "Not a valid type", + "@#139081#$Ad_Axf", + }, + ) +} + +func TestCreateContactFailsOnInvalidContent(t *testing.T) { + AssertCreateBadContactDataFails(t, + "content", + []interface{}{ + "Not a valid url", + "@#139081#$Ad_Axf", + }, + ) +} + +func TestPutContactFailsOnClubIdNotExist(t *testing.T) { + appAssert, _, _ := CreateSampleClub(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester(h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", uuid), + Body: SampleContactFactory(), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + }, + }, + ).Close() +} + +func TestPutContactUpdatesExistingContact(t *testing.T) { + appAssert, clubUUID, contactUUID := CreateSampleContact(h.InitTest(t)) + + updatedContact := SampleContactFactory() + (*updatedContact)["content"] = "nedFlanders@gmail.com" + + appAssert.TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID), + Body: updatedContact, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbContact models.Contact + + err := eaa.App.Conn.Where("id = ?", contactUUID).First(&dbContact).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(dbContact.Content, (*updatedContact)["content"]) + }, + }, + ).Close() +} + +func TestGetClubContacts(t *testing.T) { + appAssert, clubUUID, _ := CreateManyContacts(h.InitTest(t)) + + appAssert.TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respContacts []models.Contact + var dbContacts []models.Contact + err := json.NewDecoder(resp.Body).Decode(&respContacts) + eaa.Assert.NilError(err) + + err = eaa.App.Conn.Where("club_id = ?", clubUUID).Find(&dbContacts).Error + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(respContacts), len(dbContacts)) + }, + }, + ) + + appAssert.Close() +} diff --git a/backend/tests/api/club_test.go b/backend/tests/api/club_test.go index bcb22d305..8539d71b6 100644 --- a/backend/tests/api/club_test.go +++ b/backend/tests/api/club_test.go @@ -9,8 +9,10 @@ import ( "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/transactions" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" "github.com/goccy/go-json" "github.com/gofiber/fiber/v2" + "github.com/google/uuid" "github.com/huandu/go-assert" "gorm.io/gorm" ) @@ -31,7 +33,7 @@ func BadEmailPOCFactory() *map[string]interface{} { } } -func AssertPOCWithBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, body *map[string]interface{}) { +func AssertPOCWithBodyRespDB(app h.TestApp, assert *assert.A, resp *http.Response, body *map[string]interface{}) { var respPOC models.PointOfContact err := json.NewDecoder(resp.Body).Decode(&respPOC) @@ -51,27 +53,39 @@ func AssertPOCWithBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, assert.Equal((*body)["position"].(string), dbPOC.Position) } -func AssertSamplePOCBodyRespDB(app TestApp, assert *assert.A, resp *http.Response) { +func AssertSamplePOCBodyRespDB(app h.TestApp, assert *assert.A, resp *http.Response) { AssertPOCWithBodyRespDB(app, assert, resp, SamplePOCFactory()) } -func CreateSamplePOC(t *testing.T) ExistingAppAssert { - return TestRequest{ - Method: fiber.MethodPut, - Path: "/api/v1/clubs/1/poc/", - Body: SamplePOCFactory(), - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 200, - DBTester: AssertSamplePOCBodyRespDB, +func CreateSamplePOC(t *testing.T, existingAppAssert h.ExistingAppAssert) (h.ExistingAppAssert, uuid.UUID) { + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + var pocId uuid.UUID + + newAppAssert := existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubUUID), + Body: SamplePOCFactory(), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respPOC models.PointOfContact + err := json.NewDecoder(resp.Body).Decode(&respPOC) + eaa.Assert.NilError(err) + pocId = respPOC.ID + }, }, ) + return newAppAssert, pocId } -func CreateInvalidEmailPOC(t *testing.T) ExistingAppAssert { - return TestRequest{ +func CreateInvalidEmailPOC(t *testing.T) h.ExistingAppAssert { + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + return h.TestRequest{ Method: fiber.MethodPut, - Path: "/api/v1/clubs/1/poc/", + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubUUID), Body: BadEmailPOCFactory(), }.TestOnStatusAndDB(t, nil, DBTesterWithStatus{ @@ -91,7 +105,8 @@ func TestCreatePOCFailsOnInvalidEmail(t *testing.T) { } func TestUpdatePOCWorks(t *testing.T) { - appAssert := CreateSamplePOC(t) + appAssert := CreateSamplePOC(h.InitTest(t)) + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) id := 1 newName := "Jane Austen" @@ -106,12 +121,12 @@ func TestUpdatePOCWorks(t *testing.T) { TestRequest{ Method: fiber.MethodPut, - Path: fmt.Sprintf("/api/v1/clubs/%d/poc/", id), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubUUID), Body: &requestBody, }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + DBTester: func(app h.TestApp, assert *assert.A, resp *http.Response) { var club models.Club err := app.Conn.First(&club, id).Error assert.NilError(err) @@ -138,17 +153,217 @@ func TestUpdatePOCWorks(t *testing.T) { ).Close() } +func SampleClubFactory(userID *uuid.UUID) *map[string]interface{} { + return &map[string]interface{}{ + "user_id": userID, + "name": "Generate", + "preview": "Generate is Northeastern's premier student-led product development studio.", + "description": "https://mongodb.com", + "is_recruiting": true, + "recruitment_cycle": "always", + "recruitment_type": "application", + "application_link": "https://generatenu.com/apply", + "logo": "https://aws.amazon.com/s3/", + } +} + +func AssertClubBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { + var respClub models.Club + + err := json.NewDecoder(resp.Body).Decode(&respClub) + + eaa.Assert.NilError(err) + + var dbClubs []models.Club + + err = eaa.App.Conn.Order("created_at desc").Find(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(2, len(dbClubs)) + + dbClub := dbClubs[0] + + eaa.Assert.Equal(dbClub.ID, respClub.ID) + eaa.Assert.Equal(dbClub.Name, respClub.Name) + eaa.Assert.Equal(dbClub.Preview, respClub.Preview) + eaa.Assert.Equal(dbClub.Description, respClub.Description) + eaa.Assert.Equal(dbClub.NumMembers, respClub.NumMembers) + eaa.Assert.Equal(dbClub.IsRecruiting, respClub.IsRecruiting) + eaa.Assert.Equal(dbClub.RecruitmentCycle, respClub.RecruitmentCycle) + eaa.Assert.Equal(dbClub.RecruitmentType, respClub.RecruitmentType) + eaa.Assert.Equal(dbClub.ApplicationLink, respClub.ApplicationLink) + eaa.Assert.Equal(dbClub.Logo, respClub.Logo) + + var dbAdmins []models.User + + err = eaa.App.Conn.Model(&dbClub).Association("Admin").Find(&dbAdmins) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbAdmins)) + + eaa.Assert.Equal(*(*body)["user_id"].(*uuid.UUID), dbAdmins[0].ID) + eaa.Assert.Equal((*body)["name"].(string), dbClub.Name) + eaa.Assert.Equal((*body)["preview"].(string), dbClub.Preview) + eaa.Assert.Equal((*body)["description"].(string), dbClub.Description) + eaa.Assert.Equal((*body)["is_recruiting"].(bool), dbClub.IsRecruiting) + eaa.Assert.Equal(models.RecruitmentCycle((*body)["recruitment_cycle"].(string)), dbClub.RecruitmentCycle) + eaa.Assert.Equal(models.RecruitmentType((*body)["recruitment_type"].(string)), dbClub.RecruitmentType) + eaa.Assert.Equal((*body)["application_link"].(string), dbClub.ApplicationLink) + eaa.Assert.Equal((*body)["logo"].(string), dbClub.Logo) + + return dbClub.ID +} + +func AssertClubWithBodyRespDBMostRecent(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { + var respClub models.Club + + err := json.NewDecoder(resp.Body).Decode(&respClub) + + eaa.Assert.NilError(err) + + var dbClub models.Club + + err = eaa.App.Conn.Order("created_at desc").First(&dbClub).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(dbClub.ID, respClub.ID) + eaa.Assert.Equal(dbClub.Name, respClub.Name) + eaa.Assert.Equal(dbClub.Preview, respClub.Preview) + eaa.Assert.Equal(dbClub.Description, respClub.Description) + eaa.Assert.Equal(dbClub.NumMembers, respClub.NumMembers) + eaa.Assert.Equal(dbClub.IsRecruiting, respClub.IsRecruiting) + eaa.Assert.Equal(dbClub.RecruitmentCycle, respClub.RecruitmentCycle) + eaa.Assert.Equal(dbClub.RecruitmentType, respClub.RecruitmentType) + eaa.Assert.Equal(dbClub.ApplicationLink, respClub.ApplicationLink) + eaa.Assert.Equal(dbClub.Logo, respClub.Logo) + + var dbAdmins []models.User + + err = eaa.App.Conn.Model(&dbClub).Association("Admins").Find(&dbAdmins) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbAdmins)) + + dbAdmin := dbAdmins[0] + + eaa.Assert.Equal((*body)["user_id"].(uuid.UUID), dbAdmin.ID) + eaa.Assert.Equal((*body)["name"].(string), dbClub.Name) + eaa.Assert.Equal((*body)["preview"].(string), dbClub.Preview) + eaa.Assert.Equal((*body)["description"].(string), dbClub.Description) + eaa.Assert.Equal((*body)["num_members"].(int), dbClub.NumMembers) + eaa.Assert.Equal((*body)["is_recruiting"].(bool), dbClub.IsRecruiting) + eaa.Assert.Equal((*body)["recruitment_cycle"].(string), dbClub.RecruitmentCycle) + eaa.Assert.Equal((*body)["recruitment_type"].(string), dbClub.RecruitmentType) + eaa.Assert.Equal((*body)["application_link"].(string), dbClub.ApplicationLink) + eaa.Assert.Equal((*body)["logo"].(string), dbClub.Logo) + + return dbClub.ID +} + +func AssertSampleClubBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, userID uuid.UUID) uuid.UUID { + sampleClub := SampleClubFactory(&userID) + (*sampleClub)["num_members"] = 1 + + return AssertClubBodyRespDB(eaa, resp, sampleClub) +} + +func CreateSampleClub(existingAppAssert h.ExistingAppAssert) (eaa h.ExistingAppAssert, studentUUID uuid.UUID, clubUUID uuid.UUID) { + var sampleClubUUID uuid.UUID + + newAppAssert := existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/clubs/", + Body: SampleClubFactory(nil), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer("user_id"), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + sampleClubUUID = AssertSampleClubBodyRespDB(eaa, resp, eaa.App.TestUser.UUID) + }, + }, + ) + + return existingAppAssert, newAppAssert.App.TestUser.UUID, sampleClubUUID +} + +func TestCreateClubWorks(t *testing.T) { + existingAppAssert, _, _ := CreateSampleClub(h.InitTest(t)) + existingAppAssert.Close() +} + +func TestGetClubsWorks(t *testing.T) { + h.InitTest(t).TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/clubs/", + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respClubs []models.Club + + err := json.NewDecoder(resp.Body).Decode(&respClubs) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(respClubs)) + + respClub := respClubs[0] + + var dbClubs []models.Club + + err = eaa.App.Conn.Order("created_at desc").Find(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbClubs)) + + dbClub := dbClubs[0] + + eaa.Assert.Equal(dbClub.ID, respClub.ID) + eaa.Assert.Equal(dbClub.Name, respClub.Name) + eaa.Assert.Equal(dbClub.Preview, respClub.Preview) + eaa.Assert.Equal(dbClub.Description, respClub.Description) + eaa.Assert.Equal(dbClub.NumMembers, respClub.NumMembers) + eaa.Assert.Equal(dbClub.IsRecruiting, respClub.IsRecruiting) + eaa.Assert.Equal(dbClub.RecruitmentCycle, respClub.RecruitmentCycle) + eaa.Assert.Equal(dbClub.RecruitmentType, respClub.RecruitmentType) + eaa.Assert.Equal(dbClub.ApplicationLink, respClub.ApplicationLink) + eaa.Assert.Equal(dbClub.Logo, respClub.Logo) + + eaa.Assert.Equal("SAC", dbClub.Name) + eaa.Assert.Equal("SAC", dbClub.Preview) + eaa.Assert.Equal("SAC", dbClub.Description) + eaa.Assert.Equal(1, dbClub.NumMembers) + eaa.Assert.Equal(true, dbClub.IsRecruiting) + eaa.Assert.Equal(models.Always, dbClub.RecruitmentCycle) + eaa.Assert.Equal(models.Application, dbClub.RecruitmentType) + eaa.Assert.Equal("https://generatenu.com/apply", dbClub.ApplicationLink) + eaa.Assert.Equal("https://aws.amazon.com/s3", dbClub.Logo) + }, + }, + ).Close() +} + func TestUpdatePOCFailsOnInvalidBody(t *testing.T) { appAssert := CreateSamplePOC(t) + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) for _, invalidData := range []map[string]interface{}{ {"email": "not.northeastern"}, {"position": ""}, {"name": ""}, } { - TestRequest{ + h.TestRequest{ Method: fiber.MethodPut, - Path: "/api/v1/clubs/1/poc/", + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubUUID), Body: &invalidData, }.TestOnStatusMessageAndDB(t, &appAssert, StatusMessageDBTester{ @@ -163,8 +378,182 @@ func TestUpdatePOCFailsOnInvalidBody(t *testing.T) { appAssert.Close() } +func AssertNumClubsRemainsAtN(eaa h.ExistingAppAssert, resp *http.Response, n int) { + var dbClubs []models.Club + + err := eaa.App.Conn.Order("created_at desc").Find(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(n, len(dbClubs)) +} + +var TestNumClubsRemainsAt1 = func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumClubsRemainsAtN(eaa, resp, 1) +} + +func AssertCreateBadClubDataFails(t *testing.T, jsonKey string, badValues []interface{}) { + appAssert, uuid, _ := CreateSampleStudent(t, nil) + + for _, badValue := range badValues { + sampleClubPermutation := *SampleClubFactory(&uuid) + sampleClubPermutation[jsonKey] = badValue + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/clubs/", + Body: &sampleClubPermutation, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateClub, + Tester: TestNumClubsRemainsAt1, + }, + ) + } + appAssert.Close() +} + +func TestCreateClubFailsOnInvalidDescription(t *testing.T) { + AssertCreateBadClubDataFails(t, + "description", + []interface{}{ + "Not an URL", + "@#139081#$Ad_Axf", + // "https://google.com", <-- TODO fix once we handle mongo urls + }, + ) +} + +func TestCreateClubFailsOnInvalidRecruitmentCycle(t *testing.T) { + AssertCreateBadClubDataFails(t, + "recruitment_cycle", + []interface{}{ + "1234", + "garbanzo", + "@#139081#$Ad_Axf", + "https://google.com", + }, + ) +} + +func TestCreateClubFailsOnInvalidRecruitmentType(t *testing.T) { + AssertCreateBadClubDataFails(t, + "recruitment_type", + []interface{}{ + "1234", + "garbanzo", + "@#139081#$Ad_Axf", + "https://google.com", + }, + ) +} + +func TestCreateClubFailsOnInvalidApplicationLink(t *testing.T) { + AssertCreateBadClubDataFails(t, + "application_link", + []interface{}{ + "Not an URL", + "@#139081#$Ad_Axf", + }, + ) +} + +func TestCreateClubFailsOnInvalidLogo(t *testing.T) { + AssertCreateBadClubDataFails(t, + "logo", + []interface{}{ + "Not an URL", + "@#139081#$Ad_Axf", + // "https://google.com", <-- TODO uncomment once we figure out s3 url validation + }, + ) +} + +func TestUpdateClubWorks(t *testing.T) { + appAssert, studentUUID, clubUUID := CreateSampleClub(h.InitTest(t)) + + updatedClub := SampleClubFactory(&studentUUID) + (*updatedClub)["name"] = "Updated Name" + (*updatedClub)["preview"] = "Updated Preview" + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/clubs/%s", clubUUID), + Body: updatedClub, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertClubBodyRespDB(eaa, resp, updatedClub) + }, + }, + ).Close() +} + +func TestUpdateClubFailsOnInvalidBody(t *testing.T) { + appAssert, studentUUID, clubUUID := CreateSampleClub(h.InitTest(t)) + + body := SampleClubFactory(&studentUUID) + + for _, invalidData := range []map[string]interface{}{ + {"description": "Not a URL"}, + {"recruitment_cycle": "1234"}, + {"recruitment_type": "ALLLLWAYSSSS"}, + {"application_link": "Not an URL"}, + {"logo": "@12394X_2"}, + } { + invalidData := invalidData + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/clubs/%s", clubUUID), + Body: &invalidData, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateClub, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbClubs []models.Club + + err := eaa.App.Conn.Order("created_at desc").Find(&dbClubs).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(2, len(dbClubs)) + + dbClub := dbClubs[0] + + var dbAdmins []models.User + + err = eaa.App.Conn.Model(&dbClub).Association("Admin").Find(&dbAdmins) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(1, len(dbAdmins)) + + eaa.Assert.Equal(*(*body)["user_id"].(*uuid.UUID), dbAdmins[0].ID) + eaa.Assert.Equal((*body)["name"].(string), dbClub.Name) + eaa.Assert.Equal((*body)["preview"].(string), dbClub.Preview) + eaa.Assert.Equal((*body)["description"].(string), dbClub.Description) + eaa.Assert.Equal((*body)["is_recruiting"].(bool), dbClub.IsRecruiting) + eaa.Assert.Equal(models.RecruitmentCycle((*body)["recruitment_cycle"].(string)), dbClub.RecruitmentCycle) + eaa.Assert.Equal(models.RecruitmentType((*body)["recruitment_type"].(string)), dbClub.RecruitmentType) + eaa.Assert.Equal((*body)["application_link"].(string), dbClub.ApplicationLink) + eaa.Assert.Equal((*body)["logo"].(string), dbClub.Logo) + }, + }, + ) + } + appAssert.Close() +} + func TestInsertPOCFailsOnMissingFields(t *testing.T) { appAssert := CreateSamplePOC(t) + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) for _, missingField := range []string{ "name", @@ -175,7 +564,7 @@ func TestInsertPOCFailsOnMissingFields(t *testing.T) { delete(samplePOCPermutation, missingField) TestRequest{ Method: fiber.MethodPut, - Path: "/api/v1/clubs/1/poc/", + Path: fmt.Sprintf("/api/v1/clubs/1/poc/", clubUUID), Body: &samplePOCPermutation, }.TestOnStatusMessageAndDB(t, &appAssert, StatusMessageDBTester{ @@ -193,14 +582,15 @@ func TestInsertPOCFailsOnMissingFields(t *testing.T) { // GET ALL POC TEST CASES func TestGetAllPOCWorks(t *testing.T) { appAssert := CreateSamplePOC(t) + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) TestRequest{ Method: fiber.MethodGet, - Path: "/api/v1/clubs/1/poc/", + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubUUID), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + DBTester: func(app h.TestApp, assert *assert.A, resp *http.Response) { var pointOfContacts []models.PointOfContact err := json.NewDecoder(resp.Body).Decode(&pointOfContacts) @@ -224,17 +614,18 @@ func TestGetAllPOCWorks(t *testing.T) { } func TestGetAllPOCClubNotFound(t *testing.T) { - clubId := 17 - TestRequest{ + clubId := uuid.New() + + h.TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/clubs/%d/poc/", clubId), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/", clubId), }.TestOnStatusMessageAndDB(t, nil, StatusMessageDBTester{ MessageWithStatus: MessageWithStatus{ Status: 404, Message: errors.ClubNotFound, }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + DBTester: func(app h.TestApp, assert *assert.A, resp *http.Response) { var club models.Club err := app.Conn.Where("id = ?", clubId).First(&club).Error assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) @@ -245,11 +636,11 @@ func TestGetAllPOCClubNotFound(t *testing.T) { func TestGetPOCWorks(t *testing.T) { appAssert := CreateSamplePOC(t) - id := 1 + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) - TestRequest{ + h.TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/clubs/1/poc/%d", id), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%d", clubUUID, id), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: 200, @@ -274,6 +665,8 @@ func TestGetPOCWorks(t *testing.T) { } func TestGetPOCFailsBadRequest(t *testing.T) { + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + badRequests := []string{ "0", "-1", @@ -285,7 +678,7 @@ func TestGetPOCFailsBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/clubs/1/poc/%s", badRequest), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%s", clubUUID, badRequest), }.TestOnStatusAndMessage(t, nil, MessageWithStatus{ Status: 400, @@ -296,18 +689,19 @@ func TestGetPOCFailsBadRequest(t *testing.T) { } func TestGetPOCFailsNotExist(t *testing.T) { - id := uint(42) + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + id := uuid.New() TestRequest{ Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/clubs/1/poc/%d", id), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%d", clubUUID, id), }.TestOnStatusMessageAndDB(t, nil, StatusMessageDBTester{ MessageWithStatus: MessageWithStatus{ Status: 404, Message: errors.PointOfContactNotFound, }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + DBTester: func(app h.TestApp, assert *assert.A, resp *http.Response) { var pointOfContact models.PointOfContact err := app.Conn.Where("id = ?", id).First(&pointOfContact).Error assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) @@ -319,10 +713,12 @@ func TestGetPOCFailsNotExist(t *testing.T) { // DELETE TEST CASES func TestDeletePointOfContactWorks(t *testing.T) { appAssert := CreateSamplePOC(t) + // how to get pocId + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) TestRequest{ Method: fiber.MethodDelete, - Path: "/api/v1/clubs/1/poc/1", + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%s", clubUUID, pocId), }.TestOnStatusAndDB(t, &appAssert, DBTesterWithStatus{ Status: 204, @@ -343,17 +739,19 @@ func TestDeletePOCClubIDBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/clubs/%s/poc/doe.jane@northeastern.edu", badRequest), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/1", badRequest), }.TestOnStatusAndMessage(t, nil, MessageWithStatus{ Status: 400, - Message: errors.FailedToValidateClubId, + Message: errors.FailedToValidateClub, }, ).Close() } } func TestDeletePOCBadRequest(t *testing.T) { + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + badRequests := []string{ "0", "-1", @@ -365,7 +763,7 @@ func TestDeletePOCBadRequest(t *testing.T) { for _, badRequest := range badRequests { TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/clubs/1/poc/%s", badRequest), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%s", clubUUID, badRequest), }.TestOnStatusAndMessage(t, nil, MessageWithStatus{ Status: 400, @@ -376,18 +774,18 @@ func TestDeletePOCBadRequest(t *testing.T) { } func TestDeletePOCClubNotExist(t *testing.T) { - pocId := 1 + clubUUID := uuid.New() TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/clubs/3/poc/%d", pocId), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%s", clubUUID, pocId), }.TestOnStatusMessageAndDB(t, nil, StatusMessageDBTester{ MessageWithStatus: MessageWithStatus{ Status: 404, Message: errors.ClubNotFound, }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + DBTester: func(app h.TestApp, assert *assert.A, resp *http.Response) { var pointOfContact models.PointOfContact err := app.Conn.Where("id = ?", pocId).First(&pointOfContact).Error @@ -399,11 +797,12 @@ func TestDeletePOCClubNotExist(t *testing.T) { } func TestDeletePOCNotExist(t *testing.T) { - pocId := 6 + _, _, clubUUID := CreateSampleClub(h.InitTest(t)) + pocUUID := uuid.New() TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/clubs/1/poc/%d", pocId), + Path: fmt.Sprintf("/api/v1/clubs/%s/poc/%s", clubUUID, pocUUID), }.TestOnStatusMessageAndDB(t, nil, StatusMessageDBTester{ MessageWithStatus: MessageWithStatus{ @@ -433,11 +832,11 @@ func AssertNumPOCRemainsAtN(app TestApp, assert *assert.A, resp *http.Response, } // assert remaining POC = 1 -var TestNumPOCRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) { +var TestNumPOCRemainsAt1 = func(app h.TestApp, assert *assert.A, resp *http.Response) { AssertNumPOCRemainsAtN(app, assert, resp, 1) } -var TestNumPOCRemainsAt0 = func(app TestApp, assert *assert.A, resp *http.Response) { +var TestNumPOCRemainsAt0 = func(app h.TestApp, assert *assert.A, resp *http.Response) { AssertNumPOCRemainsAtN(app, assert, resp, 0) } @@ -464,3 +863,114 @@ func AssertCreatePOCBadDataFails(t *testing.T, jsonKey string, badValues []inter } appAssert.Close() } + +func TestUpdateClubFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + sampleStudent, rawPassword := h.SampleStudentFactory() + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/clubs/%s", badRequest), + Body: h.SampleStudentJSONFactory(sampleStudent, rawPassword), + Role: &models.Super, + }, + errors.FailedToValidateID, + ) + } + appAssert.Close() +} + +func TestUpdateClubFailsOnClubIdNotExist(t *testing.T) { + uuid := uuid.New() + + h.InitTest(t).TestOnErrorAndTester(h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/clubs/%s", uuid), + Body: SampleClubFactory(nil), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer("user_id"), + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + }, + }, + ).Close() +} + +func TestDeleteClubWorks(t *testing.T) { + appAssert, _, clubUUID := CreateSampleClub(h.InitTest(t)) + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/clubs/%s", clubUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: TestNumClubsRemainsAt1, + }, + ).Close() +} + +func TestDeleteClubNotExist(t *testing.T) { + uuid := uuid.New() + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/clubs/%s", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ClubNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var club models.Club + + err := eaa.App.Conn.Where("id = ?", uuid).First(&club).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + + AssertNumClubsRemainsAtN(eaa, resp, 1) + }, + }, + ).Close() +} + +func TestDeleteClubBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + + badRequests := []string{ + "0", + "-1", + "1.1", + "hello", + "null", + } + + for _, badRequest := range badRequests { + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/clubs/%s", badRequest), + Role: &models.Super, + }, + errors.FailedToValidateID, + ) + } + appAssert.Close() +} diff --git a/backend/tests/api/contact_test.go b/backend/tests/api/contact_test.go new file mode 100644 index 000000000..4dfc76a22 --- /dev/null +++ b/backend/tests/api/contact_test.go @@ -0,0 +1,258 @@ +package tests + +import ( + stdliberrors "errors" + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "gorm.io/gorm" +) + +func SampleContactFactory() *map[string]interface{} { + return &map[string]interface{}{ + "type": "email", + "content": "jermaine@gmail.com", + } +} + +func ManyContactsFactory() map[string](*map[string]interface{}) { + arr := make(map[string]*map[string]interface{}) + + arr["email"] = &map[string]interface{}{ + "type": "email", + "content": "cheeseClub@gmail.com", + } + + arr["youtube"] = &map[string]interface{}{ + "type": "youtube", + "content": "https://youtube.com/cheeseClub", + } + + arr["facebook"] = &map[string]interface{}{ + "type": "facebook", + "content": "https://facebook.com/cheeseClub", + } + + arr["discord"] = &map[string]interface{}{ + "type": "discord", + "content": "https://discord.com/cheeseClub", + } + + arr["instagram"] = &map[string]interface{}{ + "type": "instagram", + "content": "https://instagram.com/cheeseClub", + } + arr["github"] = &map[string]interface{}{ + "type": "github", + "content": "https://github.com/cheeseClub", + } + + return arr +} + +func AssertContactBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { + var respContact models.Contact + + err := json.NewDecoder(resp.Body).Decode(&respContact) + + eaa.Assert.NilError(err) + + var dbContacts []models.Contact + + err = eaa.App.Conn.Order("created_at desc").Find(&dbContacts).Error + + eaa.Assert.NilError(err) + + dbContact := dbContacts[0] + + eaa.Assert.Equal(dbContact.ID, respContact.ID) + eaa.Assert.Equal(dbContact.Type, respContact.Type) + eaa.Assert.Equal(dbContact.Content, respContact.Content) + + return dbContact.ID +} + +func CreateSampleContact(existingAppAssert h.ExistingAppAssert) (eaa h.ExistingAppAssert, clubUUID uuid.UUID, contactUUID uuid.UUID) { + appAssert, _, clubUUID := CreateSampleClub(existingAppAssert) + + var sampleContactUUID uuid.UUID + + return appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID), + Body: SampleContactFactory(), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + sampleContactUUID = AssertContactBodyRespDB(eaa, resp, SampleContactFactory()) + AssertNumContactsRemainsAtN(eaa, resp, 1) + }, + }, + ), clubUUID, sampleContactUUID +} + +func CreateManyContacts(existingAppAssert h.ExistingAppAssert) (eaa h.ExistingAppAssert, clubUUID uuid.UUID, contactUUIDs map[string]uuid.UUID) { + existingAppAssert, _, clubUUID = CreateSampleClub(existingAppAssert) + + contactUUIDs = make(map[string]uuid.UUID) + + currentLength := 0 + for key, contact := range ManyContactsFactory() { + existingAppAssert = existingAppAssert.TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodPut, + Path: fmt.Sprintf("/api/v1/clubs/%s/contacts", clubUUID), + Body: contact, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + contactUUIDs[key] = AssertContactBodyRespDB(eaa, resp, contact) + currentLength++ + AssertNumContactsRemainsAtN(eaa, resp, currentLength) + }, + }, + ) + } + + return existingAppAssert, clubUUID, contactUUIDs +} + +func TestCreateManyContactsWorks(t *testing.T) { + existingAppAssert, _, _ := CreateManyContacts(h.InitTest(t)) + existingAppAssert.Close() +} + +func TestCreateContactWorks(t *testing.T) { + existingAppAssert, _, _ := CreateSampleContact(h.InitTest(t)) + existingAppAssert.Close() +} + +func AssertNumContactsRemainsAtN(eaa h.ExistingAppAssert, resp *http.Response, n int) { + var dbContacts []models.Contact + + err := eaa.App.Conn.Order("created_at desc").Find(&dbContacts).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(n, len(dbContacts)) +} + +func TestGetContactByIdWorks(t *testing.T) { + appAssert, _, contactUUID := CreateSampleContact(h.InitTest(t)) + + appAssert.TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/contacts/%s", contactUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respContact models.Contact + + err := json.NewDecoder(resp.Body).Decode(&respContact) + + eaa.Assert.NilError(err) + + var dbContacts []models.Contact + + err = eaa.App.Conn.Order("created_at desc").Find(&dbContacts).Error + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(dbContacts[0].ID, respContact.ID) + eaa.Assert.Equal(dbContacts[0].Type, respContact.Type) + eaa.Assert.Equal(dbContacts[0].Content, respContact.Content) + }, + }, + ).Close() +} + +func TestGetContactFailsOnContactIdNotExist(t *testing.T) { + appAssert, _, _ := CreateSampleContact(h.InitTest(t)) + + uuid := uuid.New() + + appAssert.TestOnErrorAndTester(h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/contacts/%s", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ContactNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var contact models.Contact + + err := eaa.App.Conn.Where("id = ?", uuid).First(&contact).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + }, + }, + ).Close() +} + +func TestDeleteContactWorks(t *testing.T) { + appAssert, _, contactUUID := CreateSampleContact(h.InitTest(t)) + + appAssert.TestOnStatusAndTester(h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/contacts/%s", contactUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var contact models.Contact + + err := eaa.App.Conn.Where("id = ?", contactUUID).First(&contact).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + }, + }, + ).Close() +} + +func TestDeleteContactFailsOnContactIdNotExist(t *testing.T) { + appAssert, _, _ := CreateSampleContact(h.InitTest(t)) + uuid := uuid.New() + + appAssert.TestOnErrorAndTester(h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/contacts/%s", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.ContactNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var contact models.Contact + err := eaa.App.Conn.Where("id = ?", uuid).First(&contact).Error + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + + AssertNumContactsRemainsAtN(eaa, resp, 1) + }, + }, + ).Close() +} + +// test that the request returns paginated contacts +func TestGetContactsWorks(t *testing.T) { + appAssert, _, _ := CreateManyContacts(h.InitTest(t)) + + appAssert.TestOnStatus(h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/contacts", + Role: &models.Super, + }, fiber.StatusOK, + ).Close() +} diff --git a/backend/tests/api/health_test.go b/backend/tests/api/health_test.go index d76d0b296..52e51fe9d 100644 --- a/backend/tests/api/health_test.go +++ b/backend/tests/api/health_test.go @@ -3,14 +3,16 @@ package tests import ( "testing" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" "github.com/gofiber/fiber/v2" ) func TestHealthWorks(t *testing.T) { - TestRequest{ - Method: fiber.MethodGet, - Path: "/health", - }.TestOnStatus(t, nil, - 200, + h.InitTest(t).TestOnStatus( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/health", + }, + fiber.StatusOK, ).Close() } diff --git a/backend/tests/api/helpers.go b/backend/tests/api/helpers.go deleted file mode 100644 index ec6261b9c..000000000 --- a/backend/tests/api/helpers.go +++ /dev/null @@ -1,268 +0,0 @@ -package tests - -import ( - "bytes" - crand "crypto/rand" - "fmt" - "math/big" - "net" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/GenerateNU/sac/backend/src/config" - "github.com/GenerateNU/sac/backend/src/database" - "github.com/GenerateNU/sac/backend/src/server" - - "github.com/goccy/go-json" - - "github.com/gofiber/fiber/v2" - "github.com/huandu/go-assert" - gormPostgres "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -func InitTest(t *testing.T) (TestApp, *assert.A) { - assert := assert.New(t) - app, err := spawnApp() - - assert.NilError(err) - - return app, assert -} - -type TestApp struct { - App *fiber.App - Address string - Conn *gorm.DB - Settings config.Settings - InitialDBName string -} - -func spawnApp() (TestApp, error) { - listener, err := net.Listen("tcp", "127.0.0.1:0") - - if err != nil { - return TestApp{}, err - } - - configuration, err := config.GetConfiguration("../../../config") - - if err != nil { - return TestApp{}, err - } - - initialDBName := configuration.Database.DatabaseName - - configuration.Database.DatabaseName = generateRandomDBName() - - connectionWithDB, err := configureDatabase(configuration) - - if err != nil { - return TestApp{}, err - } - - return TestApp{ - App: server.Init(connectionWithDB), - Address: fmt.Sprintf("http://%s", listener.Addr().String()), - Conn: connectionWithDB, - Settings: configuration, - InitialDBName: initialDBName, - }, nil -} -func generateRandomInt(max int64) int64 { - randInt, _ := crand.Int(crand.Reader, big.NewInt(max)) - return randInt.Int64() -} - -func generateRandomDBName() string { - prefix := "sac_test_" - letterBytes := "abcdefghijklmnopqrstuvwxyz" - length := len(prefix) + 36 - result := make([]byte, length) - for i := 0; i < length; i++ { - result[i] = letterBytes[generateRandomInt(int64(len(letterBytes)))] - } - - return fmt.Sprintf("%s%s", prefix, string(result)) -} - -func configureDatabase(config config.Settings) (*gorm.DB, error) { - dsnWithoutDB := config.Database.WithoutDb() - dbWithoutDB, err := gorm.Open(gormPostgres.Open(dsnWithoutDB), &gorm.Config{SkipDefaultTransaction: true, TranslateError: true}) - if err != nil { - return nil, err - } - - err = dbWithoutDB.Exec(fmt.Sprintf("CREATE DATABASE %s;", config.Database.DatabaseName)).Error - if err != nil { - return nil, err - } - - dsnWithDB := config.Database.WithDb() - dbWithDB, err := gorm.Open(gormPostgres.Open(dsnWithDB), &gorm.Config{SkipDefaultTransaction: true, TranslateError: true}) - - if err != nil { - return nil, err - } - - err = database.MigrateDB(config, dbWithDB) - - if err != nil { - return nil, err - } - - return dbWithDB, nil -} - -type ExistingAppAssert struct { - App TestApp - Assert *assert.A -} - -func (eaa ExistingAppAssert) Close() { - db, err := eaa.App.Conn.DB() - - if err != nil { - panic(err) - } - - err = db.Close() - - if err != nil { - panic(err) - } -} - -type TestRequest struct { - Method string - Path string - Body *map[string]interface{} - Headers *map[string]string -} - -func (request TestRequest) Test(t *testing.T, existingAppAssert *ExistingAppAssert) (ExistingAppAssert, *http.Response) { - var app TestApp - var assert *assert.A - - if existingAppAssert == nil { - app, assert = InitTest(t) - } else { - app, assert = existingAppAssert.App, existingAppAssert.Assert - } - - address := fmt.Sprintf("%s%s", app.Address, request.Path) - - var req *http.Request - - if request.Body == nil { - req = httptest.NewRequest(request.Method, address, nil) - } else { - bodyBytes, err := json.Marshal(request.Body) - - assert.NilError(err) - - req = httptest.NewRequest(request.Method, address, bytes.NewBuffer(bodyBytes)) - - if request.Headers == nil { - request.Headers = &map[string]string{} - } - - if _, ok := (*request.Headers)["Content-Type"]; !ok { - (*request.Headers)["Content-Type"] = "application/json" - } - } - - if request.Headers != nil { - for key, value := range *request.Headers { - req.Header.Add(key, value) - } - } - - resp, err := app.App.Test(req) - - assert.NilError(err) - - return ExistingAppAssert{ - App: app, - Assert: assert, - }, resp -} - -func (request TestRequest) TestOnStatus(t *testing.T, existingAppAssert *ExistingAppAssert, status int) ExistingAppAssert { - appAssert, resp := request.Test(t, existingAppAssert) - - _, assert := appAssert.App, appAssert.Assert - - assert.Equal(status, resp.StatusCode) - - return appAssert -} - -type MessageWithStatus struct { - Status int - Message string -} - -func (request TestRequest) TestOnStatusAndMessage(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) ExistingAppAssert { - appAssert, resp := request.Test(t, existingAppAssert) - assert := appAssert.Assert - - var respBody map[string]interface{} - - err := json.NewDecoder(resp.Body).Decode(&respBody) - - assert.NilError(err) - - assert.Equal(messagedStatus.Message, respBody["error"].(string)) - - assert.Equal(messagedStatus.Status, resp.StatusCode) - - return appAssert -} - -type StatusMessageDBTester struct { - MessageWithStatus MessageWithStatus - DBTester DBTester -} - -func (request TestRequest) TestOnStatusMessageAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, statusMessageDBTester StatusMessageDBTester) ExistingAppAssert { - appAssert := request.TestOnStatusAndMessage(t, existingAppAssert, statusMessageDBTester.MessageWithStatus) - statusMessageDBTester.DBTester(appAssert.App, appAssert.Assert, nil) - return appAssert -} - -type DBTester func(app TestApp, assert *assert.A, resp *http.Response) - -type DBTesterWithStatus struct { - Status int - DBTester -} - -func (request TestRequest) TestOnStatusAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert { - appAssert, resp := request.Test(t, existingAppAssert) - app, assert := appAssert.App, appAssert.Assert - - assert.Equal(dbTesterStatus.Status, resp.StatusCode) - - dbTesterStatus.DBTester(app, assert, resp) - - return appAssert -} - -func generateCasingPermutations(word string, currentPermutation string, index int, results *[]string) { - if index == len(word) { - *results = append(*results, currentPermutation) - return - } - - generateCasingPermutations(word, currentPermutation+strings.ToLower(string(word[index])), index+1, results) - generateCasingPermutations(word, currentPermutation+strings.ToUpper(string(word[index])), index+1, results) -} - -func AllCasingPermutations(word string) []string { - results := make([]string, 0) - generateCasingPermutations(word, "", 0, &results) - return results -} diff --git a/backend/tests/api/helpers/app.go b/backend/tests/api/helpers/app.go new file mode 100644 index 000000000..68b4acd55 --- /dev/null +++ b/backend/tests/api/helpers/app.go @@ -0,0 +1,64 @@ +package helpers + +import ( + "fmt" + "net" + "path/filepath" + + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/server" + "github.com/gofiber/fiber/v2" + "github.com/huandu/go-assert" + "gorm.io/gorm" +) + +type TestApp struct { + App *fiber.App + Address string + Conn *gorm.DB + Settings config.Settings + TestUser *TestUser +} + +func spawnApp() (*TestApp, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + + configuration, err := config.GetConfiguration(filepath.Join("..", "..", "..", "config")) + if err != nil { + return nil, err + } + + configuration.Database.DatabaseName = generateRandomDBName() + + connectionWithDB, err := configureDatabase(*configuration) + if err != nil { + return nil, err + } + + return &TestApp{ + App: server.Init(connectionWithDB, *configuration), + Address: fmt.Sprintf("http://%s", listener.Addr().String()), + Conn: connectionWithDB, + Settings: *configuration, + }, nil +} + +type ExistingAppAssert struct { + App TestApp + Assert *assert.A +} + +func (eaa ExistingAppAssert) Close() { + db, err := eaa.App.Conn.DB() + if err != nil { + panic(err) + } + + err = db.Close() + if err != nil { + panic(err) + } +} diff --git a/backend/tests/api/helpers/auth.go b/backend/tests/api/helpers/auth.go new file mode 100644 index 000000000..eb65640e3 --- /dev/null +++ b/backend/tests/api/helpers/auth.go @@ -0,0 +1,166 @@ +package helpers + +import ( + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/database" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +type TestUser struct { + UUID uuid.UUID + Email string + Password string + AccessToken string + RefreshToken string +} + +func (app *TestApp) Auth(role models.UserRole) { + if role == models.Super { + app.authSuper() + } else if role == models.Student { + app.authStudent() + } + // unauthed -> do nothing +} + +func (app *TestApp) authSuper() { + superUser, superUserErr := database.SuperUser(app.Settings.SuperUser) + if superUserErr != nil { + panic(superUserErr) + } + + email := superUser.Email + password := app.Settings.SuperUser.Password + + resp, err := app.Send(TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/auth/login", + Body: &map[string]interface{}{ + "email": email, + "password": password.Expose(), + }, + }) + if err != nil { + panic(err) + } + + var accessToken string + var refreshToken string + + for _, cookie := range resp.Cookies() { + if cookie.Name == "access_token" { + accessToken = cookie.Value + } else if cookie.Name == "refresh_token" { + refreshToken = cookie.Value + } + } + + if accessToken == "" || refreshToken == "" { + panic("Failed to authenticate super user") + } + + app.TestUser = &TestUser{ + UUID: database.SuperUserUUID, + Email: email, + Password: password.Expose(), + AccessToken: accessToken, + RefreshToken: refreshToken, + } +} + +func (app *TestApp) authStudent() { + studentUser, rawPassword := SampleStudentFactory() + + resp, err := app.Send(TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/", + Body: SampleStudentJSONFactory(studentUser, rawPassword), + }) + if err != nil { + panic(err) + } + var respBody map[string]interface{} + + err = json.NewDecoder(resp.Body).Decode(&respBody) + if err != nil { + panic(err) + } + + rawStudentUserUUID := respBody["id"].(string) + studentUserUUID, err := uuid.Parse(rawStudentUserUUID) + if err != nil { + panic(err) + } + + resp, err = app.Send(TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/auth/login", + Body: &map[string]interface{}{ + "email": studentUser.Email, + "password": rawPassword, + }, + }) + if err != nil { + panic(err) + } + + var accessToken string + var refreshToken string + + for _, cookie := range resp.Cookies() { + if cookie.Name == "access_token" { + accessToken = cookie.Value + } else if cookie.Name == "refresh_token" { + refreshToken = cookie.Value + } + } + + if accessToken == "" || refreshToken == "" { + panic("Failed to authenticate sample student user") + } + + app.TestUser = &TestUser{ + UUID: studentUserUUID, + Email: studentUser.Email, + Password: rawPassword, + AccessToken: accessToken, + RefreshToken: refreshToken, + } +} + +func SampleStudentFactory() (models.User, string) { + password := "1234567890&" + hashedPassword, err := auth.ComputePasswordHash(password) + if err != nil { + panic(err) + } + + return models.User{ + Role: models.Student, + FirstName: "Jane", + LastName: "Doe", + Email: "doe.jane@northeastern.edu", + PasswordHash: *hashedPassword, + NUID: "001234567", + College: models.KCCS, + Year: models.Third, + }, password +} + +func SampleStudentJSONFactory(sampleStudent models.User, rawPassword string) *map[string]interface{} { + if sampleStudent.Role != models.Student { + panic("User is not a student") + } + return &map[string]interface{}{ + "first_name": sampleStudent.FirstName, + "last_name": sampleStudent.LastName, + "email": sampleStudent.Email, + "password": rawPassword, + "nuid": sampleStudent.NUID, + "college": string(sampleStudent.College), + "year": int(sampleStudent.Year), + } +} diff --git a/backend/tests/api/helpers/database.go b/backend/tests/api/helpers/database.go new file mode 100644 index 000000000..dd51598e0 --- /dev/null +++ b/backend/tests/api/helpers/database.go @@ -0,0 +1,46 @@ +package helpers + +import ( + "fmt" + "sync" + + "github.com/GenerateNU/sac/backend/src/config" + "github.com/GenerateNU/sac/backend/src/database" + "gorm.io/gorm" +) + +var ( + rootConn *gorm.DB + once sync.Once +) + +func RootConn(dbSettings config.DatabaseSettings) { + once.Do(func() { + var err error + rootConn, err = database.EstablishConn(dbSettings.WithoutDb()) + if err != nil { + panic(err) + } + }) +} + +func configureDatabase(settings config.Settings) (*gorm.DB, error) { + RootConn(settings.Database) + + err := rootConn.Exec(fmt.Sprintf("CREATE DATABASE %s", settings.Database.DatabaseName)).Error + if err != nil { + return nil, err + } + + dbWithDB, err := database.EstablishConn(settings.Database.WithDb()) + if err != nil { + return nil, err + } + + err = database.MigrateDB(settings, dbWithDB) + if err != nil { + return nil, err + } + + return dbWithDB, nil +} diff --git a/backend/tests/api/helpers/requests.go b/backend/tests/api/helpers/requests.go new file mode 100644 index 000000000..2071b8ef7 --- /dev/null +++ b/backend/tests/api/helpers/requests.go @@ -0,0 +1,160 @@ +package helpers + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "strings" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + + "github.com/goccy/go-json" +) + +type TestRequest struct { + Method string + Path string + Body *map[string]interface{} + Headers *map[string]string + Role *models.UserRole + TestUserIDReplaces *string +} + +func (app TestApp) Send(request TestRequest) (*http.Response, error) { + address := fmt.Sprintf("%s%s", app.Address, request.Path) + + var req *http.Request + + if request.TestUserIDReplaces != nil { + if strings.Contains(request.Path, *request.TestUserIDReplaces) { + request.Path = strings.Replace(request.Path, *request.TestUserIDReplaces, app.TestUser.UUID.String(), 1) + address = fmt.Sprintf("%s%s", app.Address, request.Path) + } + if request.Body != nil { + if _, ok := (*request.Body)[*request.TestUserIDReplaces]; ok { + (*request.Body)[*request.TestUserIDReplaces] = app.TestUser.UUID.String() + } + } + } + + if request.Body == nil { + req = httptest.NewRequest(request.Method, address, nil) + } else { + bodyBytes, err := json.Marshal(request.Body) + if err != nil { + return nil, err + } + + req = httptest.NewRequest(request.Method, address, bytes.NewBuffer(bodyBytes)) + + if request.Headers == nil { + request.Headers = &map[string]string{} + } + + if _, ok := (*request.Headers)["Content-Type"]; !ok { + (*request.Headers)["Content-Type"] = "application/json" + } + } + + if request.Headers != nil { + for key, value := range *request.Headers { + req.Header.Add(key, value) + } + } + + if app.TestUser != nil { + req.AddCookie(&http.Cookie{ + Name: "access_token", + Value: app.TestUser.AccessToken, + }) + } + + resp, err := app.App.Test(req) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (request TestRequest) test(existingAppAssert ExistingAppAssert) (ExistingAppAssert, *http.Response) { + if existingAppAssert.App.TestUser == nil && request.Role != nil { + existingAppAssert.App.Auth(*request.Role) + } + + resp, err := existingAppAssert.App.Send(request) + + existingAppAssert.Assert.NilError(err) + + return existingAppAssert, resp +} + +func (existingAppAssert ExistingAppAssert) TestOnStatus(request TestRequest, status int) ExistingAppAssert { + appAssert, resp := request.test(existingAppAssert) + + appAssert.Assert.Equal(status, resp.StatusCode) + + return appAssert +} + +func (request *TestRequest) testOn(existingAppAssert ExistingAppAssert, status int, key string, value string) (ExistingAppAssert, *http.Response) { + appAssert, resp := request.test(existingAppAssert) + + var respBody map[string]interface{} + + err := json.NewDecoder(resp.Body).Decode(&respBody) + + appAssert.Assert.NilError(err) + + appAssert.Assert.Equal(value, respBody[key].(string)) + + appAssert.Assert.Equal(status, resp.StatusCode) + + return appAssert, resp +} + +func (existingAppAssert ExistingAppAssert) TestOnError(request TestRequest, expectedError errors.Error) ExistingAppAssert { + appAssert, _ := request.testOn(existingAppAssert, expectedError.StatusCode, "error", expectedError.Message) + return appAssert +} + +type ErrorWithTester struct { + Error errors.Error + Tester Tester +} + +func (existingAppAssert ExistingAppAssert) TestOnErrorAndTester(request TestRequest, errorWithTester ErrorWithTester) ExistingAppAssert { + appAssert, resp := request.testOn(existingAppAssert, errorWithTester.Error.StatusCode, "error", errorWithTester.Error.Message) + errorWithTester.Tester(appAssert, resp) + return appAssert +} + +func (existingAppAssert ExistingAppAssert) TestOnMessage(request TestRequest, status int, message string) ExistingAppAssert { + request.testOn(existingAppAssert, status, "message", message) + return existingAppAssert +} + +func (existingAppAssert ExistingAppAssert) TestOnMessageAndTester(request TestRequest, status int, message string, tester Tester) ExistingAppAssert { + appAssert, resp := request.testOn(existingAppAssert, status, "message", message) + tester(appAssert, resp) + return appAssert +} + +type Tester func(eaa ExistingAppAssert, resp *http.Response) + +type TesterWithStatus struct { + Status int + Tester +} + +func (existingAppAssert ExistingAppAssert) TestOnStatusAndTester(request TestRequest, testerStatus TesterWithStatus) ExistingAppAssert { + appAssert, resp := request.test(existingAppAssert) + + appAssert.Assert.Equal(testerStatus.Status, resp.StatusCode) + + testerStatus.Tester(appAssert, resp) + + return appAssert +} diff --git a/backend/tests/api/helpers/test.go b/backend/tests/api/helpers/test.go new file mode 100644 index 000000000..9cc54aba5 --- /dev/null +++ b/backend/tests/api/helpers/test.go @@ -0,0 +1,19 @@ +package helpers + +import ( + "testing" + + "github.com/huandu/go-assert" +) + +func InitTest(t *testing.T) ExistingAppAssert { + assert := assert.New(t) + app, err := spawnApp() + + assert.NilError(err) + + return ExistingAppAssert{ + App: *app, + Assert: assert, + } +} diff --git a/backend/tests/api/helpers/utilities.go b/backend/tests/api/helpers/utilities.go new file mode 100644 index 000000000..037815e0f --- /dev/null +++ b/backend/tests/api/helpers/utilities.go @@ -0,0 +1,45 @@ +package helpers + +import ( + crand "crypto/rand" + "fmt" + "math/big" + "strings" +) + +func generateRandomInt(max int64) int64 { + randInt, _ := crand.Int(crand.Reader, big.NewInt(max)) + return randInt.Int64() +} + +func generateRandomDBName() string { + prefix := "sac_test_" + letterBytes := "abcdefghijklmnopqrstuvwxyz" + length := len(prefix) + 36 + result := make([]byte, length) + for i := 0; i < length; i++ { + result[i] = letterBytes[generateRandomInt(int64(len(letterBytes)))] + } + + return fmt.Sprintf("%s%s", prefix, string(result)) +} + +func generateCasingPermutations(word string, currentPermutation string, index int, results *[]string) { + if index == len(word) { + *results = append(*results, currentPermutation) + return + } + + generateCasingPermutations(word, fmt.Sprintf("%s%s", currentPermutation, strings.ToLower(string(word[index]))), index+1, results) + generateCasingPermutations(word, fmt.Sprintf("%s%s", currentPermutation, strings.ToUpper(string(word[index]))), index+1, results) +} + +func AllCasingPermutations(word string) []string { + results := make([]string, 0) + generateCasingPermutations(word, "", 0, &results) + return results +} + +func StringToPointer(s string) *string { + return &s +} diff --git a/backend/tests/api/helpers_test.go b/backend/tests/api/helpers/utilities_test.go similarity index 71% rename from backend/tests/api/helpers_test.go rename to backend/tests/api/helpers/utilities_test.go index 4d24dae67..e81adb70d 100644 --- a/backend/tests/api/helpers_test.go +++ b/backend/tests/api/helpers/utilities_test.go @@ -1,10 +1,9 @@ -package tests +package helpers import ( + "slices" "testing" - "github.com/GenerateNU/sac/backend/src/utilities" - "github.com/huandu/go-assert" ) @@ -16,6 +15,6 @@ func TestThatAllCasingPermutationsWorks(t *testing.T) { acutalPermutations := AllCasingPermutations("foo") for _, permutation := range expectedPermutations { - assert.Assert(utilities.Contains(acutalPermutations, permutation)) + assert.Assert(slices.Contains(acutalPermutations, permutation)) } } diff --git a/backend/tests/api/tag_test.go b/backend/tests/api/tag_test.go index 21e13a35e..77c42fea7 100644 --- a/backend/tests/api/tag_test.go +++ b/backend/tests/api/tag_test.go @@ -7,74 +7,97 @@ import ( "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" "github.com/gofiber/fiber/v2" - "github.com/huandu/go-assert" + "github.com/google/uuid" "github.com/goccy/go-json" ) -func SampleTagFactory() *map[string]interface{} { +func SampleTagFactory(categoryID uuid.UUID) *map[string]interface{} { return &map[string]interface{}{ "name": "Generate", - "category_id": 1, + "category_id": categoryID, } } -func AssertTagWithIDBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, id uint, body *map[string]interface{}) { +func AssertTagWithBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { var respTag models.Tag err := json.NewDecoder(resp.Body).Decode(&respTag) - assert.NilError(err) + eaa.Assert.NilError(err) var dbTag models.Tag - err = app.Conn.First(&dbTag, id).Error + err = eaa.App.Conn.First(&dbTag).Error - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(dbTag.ID, respTag.ID) - assert.Equal(dbTag.Name, respTag.Name) - assert.Equal(dbTag.CategoryID, respTag.CategoryID) + eaa.Assert.Equal(dbTag.ID, respTag.ID) + eaa.Assert.Equal(dbTag.Name, respTag.Name) + eaa.Assert.Equal(dbTag.CategoryID, respTag.CategoryID) - assert.Equal((*body)["name"].(string), dbTag.Name) - assert.Equal((*body)["category_id"].(int), int(dbTag.CategoryID)) + eaa.Assert.Equal((*body)["name"].(string), dbTag.Name) + eaa.Assert.Equal((*body)["category_id"].(uuid.UUID), dbTag.CategoryID) + + return dbTag.ID } -func AssertSampleTagBodyRespDB(app TestApp, assert *assert.A, resp *http.Response) { - AssertTagWithIDBodyRespDB(app, assert, resp, 1, SampleTagFactory()) +func AssertSampleTagBodyRespDB(t *testing.T, eaa h.ExistingAppAssert, resp *http.Response) uuid.UUID { + appAssert, uuid := CreateSampleCategory(eaa) + return AssertTagWithBodyRespDB(appAssert, resp, SampleTagFactory(uuid)) } -func CreateSampleTag(t *testing.T) ExistingAppAssert { - appAssert := CreateSampleCategory(t) - - return TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/tags/", - Body: SampleTagFactory(), - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertSampleTagBodyRespDB, +func CreateSampleTag(appAssert h.ExistingAppAssert) (existingAppAssert h.ExistingAppAssert, categoryUUID uuid.UUID, tagUUID uuid.UUID) { + appAssert, categoryUUID = CreateSampleCategory(appAssert) + + AssertSampleTagBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + tagUUID = AssertTagWithBodyRespDB(appAssert, resp, SampleTagFactory(categoryUUID)) + } + + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: SampleTagFactory(categoryUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: AssertSampleTagBodyRespDB, }, ) + + return appAssert, categoryUUID, tagUUID } func TestCreateTagWorks(t *testing.T) { - CreateSampleTag(t).Close() + appAssert, _, _ := CreateSampleTag(h.InitTest(t)) + appAssert.Close() } -var AssertNoTags = func(app TestApp, assert *assert.A, resp *http.Response) { +func AssertNumTagsRemainsAtN(eaa h.ExistingAppAssert, resp *http.Response, n int) { var tags []models.Tag - err := app.Conn.Find(&tags).Error + err := eaa.App.Conn.Find(&tags).Error + + eaa.Assert.NilError(err) - assert.NilError(err) + eaa.Assert.Equal(n, len(tags)) +} - assert.Equal(0, len(tags)) +func AssertNoTags(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumTagsRemainsAtN(eaa, resp, 0) +} + +func Assert1Tag(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumTagsRemainsAtN(eaa, resp, 1) } func TestCreateTagFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + badBodys := []map[string]interface{}{ { "name": "Generate", @@ -87,65 +110,77 @@ func TestCreateTagFailsBadRequest(t *testing.T) { } for _, badBody := range badBodys { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/tags/", - Body: &badBody, - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToParseRequestBody, - }, - DBTester: AssertNoTags, + badBody := badBody + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: &badBody, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToParseRequestBody, + Tester: AssertNoTags, }, - ).Close() + ) } + + appAssert.Close() } func TestCreateTagFailsValidation(t *testing.T) { + appAssert := h.InitTest(t) + badBodys := []map[string]interface{}{ { "name": "Generate", }, { - "category_id": 1, + "category_id": uuid.New(), }, {}, } for _, badBody := range badBodys { - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/tags/", - Body: &badBody, - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateTag, - }, - DBTester: AssertNoTags, + badBody := badBody + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: &badBody, + Role: &models.Super, }, - ).Close() + h.ErrorWithTester{ + Error: errors.FailedToValidateTag, + Tester: AssertNoTags, + }, + ) } + + appAssert.Close() } func TestGetTagWorks(t *testing.T) { - existingAppAssert := CreateSampleTag(t) - - TestRequest{ - Method: fiber.MethodGet, - Path: "/api/v1/tags/1", - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: AssertSampleTagBodyRespDB, + existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertTagWithBodyRespDB(eaa, resp, SampleTagFactory(categoryUUID)) + }, }, ).Close() } func TestGetTagFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + badRequests := []string{ "0", "-1", @@ -155,108 +190,121 @@ func TestGetTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: "failed to validate id", + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + Role: &models.Super, }, - ).Close() + errors.FailedToValidateID, + ) } + + appAssert.Close() } func TestGetTagFailsNotFound(t *testing.T) { - TestRequest{ - Method: fiber.MethodGet, - Path: "/api/v1/tags/1", - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 404, - Message: errors.TagNotFound, + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/tags/%s", uuid.New()), + Role: &models.Super, }, + errors.TagNotFound, ).Close() } func TestUpdateTagWorksUpdateName(t *testing.T) { - existingAppAssert := CreateSampleTag(t) + existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(h.InitTest(t)) - generateNUTag := *SampleTagFactory() + generateNUTag := *SampleTagFactory(categoryUUID) generateNUTag["name"] = "GenerateNU" - var AssertUpdatedTagBodyRespDB = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertTagWithIDBodyRespDB(app, assert, resp, 1, &generateNUTag) + AssertUpdatedTagBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + tagUUID = AssertTagWithBodyRespDB(eaa, resp, &generateNUTag) } - TestRequest{ - Method: fiber.MethodPatch, - Path: "/api/v1/tags/1", - Body: &generateNUTag, - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: AssertUpdatedTagBodyRespDB, + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Body: &generateNUTag, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: AssertUpdatedTagBodyRespDB, }, ).Close() } func TestUpdateTagWorksUpdateCategory(t *testing.T) { - existingAppAssert := CreateSampleTag(t) + existingAppAssert, _, tagUUID := CreateSampleTag(h.InitTest(t)) technologyCategory := *SampleCategoryFactory() - technologyCategory["category_name"] = "Technology" + technologyCategory["name"] = "Technology" + + var technologyCategoryUUID uuid.UUID - var AssertNewCategoryBodyRespDB = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertCategoryWithIDBodyRespDB(app, assert, resp, 2, &technologyCategory) + AssertNewCategoryBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + technologyCategoryUUID = AssertCategoryWithBodyRespDBMostRecent(eaa, resp, &technologyCategory) } - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/categories/", - Body: &technologyCategory, - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertNewCategoryBodyRespDB, + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &technologyCategory, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: AssertNewCategoryBodyRespDB, }, ) - technologyTag := *SampleTagFactory() - technologyTag["category_id"] = 2 + technologyTag := *SampleTagFactory(technologyCategoryUUID) - var AssertUpdatedTagBodyRespDB = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertTagWithIDBodyRespDB(app, assert, resp, 1, &technologyTag) + AssertUpdatedTagBodyRespDB := func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertTagWithBodyRespDB(eaa, resp, &technologyTag) } - TestRequest{ - Method: fiber.MethodPatch, - Path: "/api/v1/tags/1", - Body: &technologyTag, - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: AssertUpdatedTagBodyRespDB, + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Body: &technologyTag, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: AssertUpdatedTagBodyRespDB, }, ).Close() } func TestUpdateTagWorksWithSameDetails(t *testing.T) { - existingAppAssert := CreateSampleTag(t) - - TestRequest{ - Method: fiber.MethodPatch, - Path: "/api/v1/tags/1", - Body: SampleTagFactory(), - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: AssertSampleTagBodyRespDB, + existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Body: SampleTagFactory(categoryUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertTagWithBodyRespDB(eaa, resp, SampleTagFactory(categoryUUID)) + }, }, ).Close() } func TestUpdateTagFailsBadRequest(t *testing.T) { + appAssert, uuid := CreateSampleCategory(h.InitTest(t)) + badRequests := []string{ "0", "-1", @@ -266,34 +314,39 @@ func TestUpdateTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), - Body: SampleTagFactory(), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateID, + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + Body: SampleTagFactory(uuid), + Role: &models.Super, }, - ).Close() + errors.FailedToValidateID, + ) } + + appAssert.Close() } func TestDeleteTagWorks(t *testing.T) { - existingAppAssert := CreateSampleTag(t) - - TestRequest{ - Method: fiber.MethodDelete, - Path: "/api/v1/tags/1", - }.TestOnStatusAndDB(t, &existingAppAssert, - DBTesterWithStatus{ - Status: 204, - DBTester: AssertNoTags, + existingAppAssert, _, tagUUID := CreateSampleTag(h.InitTest(t)) + + existingAppAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/tags/%s", tagUUID), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: AssertNoTags, }, ).Close() } func TestDeleteTagFailsBadRequest(t *testing.T) { + appAssert, _, _ := CreateSampleTag(h.InitTest(t)) + badRequests := []string{ "0", "-1", @@ -303,26 +356,34 @@ func TestDeleteTagFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateID, + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/tags/%s", badRequest), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateID, + Tester: Assert1Tag, }, - ).Close() + ) } + + appAssert.Close() } func TestDeleteTagFailsNotFound(t *testing.T) { - TestRequest{ - Method: fiber.MethodDelete, - Path: "/api/v1/tags/1", - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 404, - Message: errors.TagNotFound, + appAssert, _, _ := CreateSampleTag(h.InitTest(t)) + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/tags/%s", uuid.New()), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.TagNotFound, + Tester: Assert1Tag, }, ).Close() } diff --git a/backend/tests/api/user_tag_test.go b/backend/tests/api/user_tag_test.go new file mode 100644 index 000000000..a7196e318 --- /dev/null +++ b/backend/tests/api/user_tag_test.go @@ -0,0 +1,361 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +func SampleCategoriesFactory() *[]map[string]interface{} { + return &[]map[string]interface{}{ + { + "name": "Business", + }, + { + "name": "STEM", + }, + } +} + +func SampleTagsFactory(categoryIDs []uuid.UUID) *[]map[string]interface{} { + lenOfIDs := len(categoryIDs) + + return &[]map[string]interface{}{ + { + "name": "Computer Science", + "category_id": categoryIDs[1%lenOfIDs], + }, + { + "name": "Mechanical Engineering", + "category_id": categoryIDs[1%lenOfIDs], + }, + { + "name": "Finance", + "category_id": categoryIDs[0%lenOfIDs], + }, + } +} + +func SampleTagIDsFactory(tagIDs *[]uuid.UUID) *map[string]interface{} { + tags := tagIDs + + if tags == nil { + tags = &[]uuid.UUID{uuid.New()} + } + + return &map[string]interface{}{ + "tags": tags, + } +} + +func CreateSetOfTags(t *testing.T, appAssert *h.ExistingAppAssert) ([]uuid.UUID, *h.ExistingAppAssert) { + if appAssert == nil { + newAppAssert := h.InitTest(t) + appAssert = &newAppAssert + } + + categories := SampleCategoriesFactory() + + categoryIDs := []uuid.UUID{} + for _, category := range *categories { + category := category + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/categories/", + Body: &category, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respCategory models.Category + + err := json.NewDecoder(resp.Body).Decode(&respCategory) + + eaa.Assert.NilError(err) + + categoryIDs = append(categoryIDs, respCategory.ID) + }, + }, + ) + } + + tags := SampleTagsFactory(categoryIDs) + + tagIDs := []uuid.UUID{} + for _, tag := range *tags { + tag := tag + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/tags/", + Body: &tag, + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respTag models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTag) + + eaa.Assert.NilError(err) + + tagIDs = append(tagIDs, respTag.ID) + }, + }, + ) + } + + return tagIDs, appAssert +} + +func AssertUserTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, id uuid.UUID) { + var respTags []models.Tag + + // Retrieve the tags from the response: + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + // Retrieve the user connected to the tags: + var dbUser models.User + err = eaa.App.Conn.First(&dbUser, id).Error + + eaa.Assert.NilError(err) + + // Retrieve the tags in the bridge table associated with the user: + var dbTags []models.Tag + err = eaa.App.Conn.Model(&dbUser).Association("Tag").Find(&dbTags) + + eaa.Assert.NilError(err) + + // Confirm all the resp tags are equal to the db tags: + for i, respTag := range respTags { + eaa.Assert.Equal(respTag.ID, dbTags[i].ID) + eaa.Assert.Equal(respTag.Name, dbTags[i].Name) + eaa.Assert.Equal(respTag.CategoryID, dbTags[i].CategoryID) + } +} + +func AssertSampleUserTagsRespDB(eaa h.ExistingAppAssert, resp *http.Response, uuid uuid.UUID) { + AssertUserTagsRespDB(eaa, resp, uuid) +} + +func TestCreateUserTagsFailsOnInvalidDataType(t *testing.T) { + // Invalid tag data types: + invalidTags := []interface{}{ + []string{"1", "2", "34"}, + []models.Tag{{Name: "Test", CategoryID: uuid.UUID{}}, {Name: "Test2", CategoryID: uuid.UUID{}}}, + []float32{1.32, 23.5, 35.1}, + } + + // Test each of the invalid tags: + for _, tag := range invalidTags { + malformedTag := *SampleTagIDsFactory(nil) + malformedTag["tags"] = tag + + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/:userID/tags/", + Body: &malformedTag, + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + errors.FailedToParseRequestBody, + ).Close() + } +} + +func TestCreateUserTagsFailsOnInvalidUserID(t *testing.T) { + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags", badRequest), + Body: SampleTagIDsFactory(nil), + Role: &models.Student, + }, + errors.FailedToValidateID, + ).Close() + } +} + +type UUIDSlice []uuid.UUID + +var testUUID = uuid.New() + +func TestCreateUserTagsFailsOnInvalidKey(t *testing.T) { + invalidBody := []map[string]interface{}{ + { + "tag": UUIDSlice{testUUID, testUUID}, + }, + { + "tagIDs": []uint{1, 2, 3}, + }, + } + + for _, body := range invalidBody { + body := body + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/:userID/tags/", + Body: &body, + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + errors.FailedToValidateUserTags, + ).Close() + } +} + +func TestCreateUserTagsFailsOnNonExistentUser(t *testing.T) { + uuid := uuid.New() + + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid), + Body: SampleTagIDsFactory(nil), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var dbUser models.User + err := eaa.App.Conn.First(&dbUser, uuid).Error + + eaa.Assert.Assert(err != nil) + }, + }, + ).Close() +} + +func TestCreateUserTagsWorks(t *testing.T) { + // Create a set of tags: + tagUUIDs, appAssert := CreateSetOfTags(t, nil) + + // Confirm adding real tags adds them to the user: + appAssert.TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/:userID/tags/", + Body: SampleTagIDsFactory(&tagUUIDs), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleUserTagsRespDB(eaa, resp, eaa.App.TestUser.UUID) + }, + }, + ) + + appAssert.Close() +} + +func TestCreateUserTagsNoneAddedIfInvalid(t *testing.T) { + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/:userID/tags/", + Body: SampleTagIDsFactory(nil), + Role: &models.Super, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(respTags), 0) + }, + }, + ).Close() +} + +func TestGetUserTagsFailsOnNonExistentUser(t *testing.T) { + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s/tags/", uuid.New()), + Role: &models.Super, + }, errors.UserNotFound, + ).Close() +} + +func TestGetUserTagsReturnsEmptyListWhenNoneAdded(t *testing.T) { + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/:userID/tags/", + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + eaa.Assert.NilError(err) + + eaa.Assert.Equal(len(respTags), 0) + }, + }, + ).Close() +} + +func TestGetUserTagsReturnsCorrectList(t *testing.T) { + tagUUIDs, appAssert := CreateSetOfTags(t, nil) + + newAppAssert := *appAssert + + newAppAssert.TestOnStatus( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/:userID/tags/", + Body: SampleTagIDsFactory(&tagUUIDs), + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + fiber.StatusCreated, + ).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/:userID/tags/", + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertSampleUserTagsRespDB(eaa, resp, eaa.App.TestUser.UUID) + }, + }, + ).Close() +} diff --git a/backend/tests/api/user_test.go b/backend/tests/api/user_test.go index 368235d54..7f790c49e 100644 --- a/backend/tests/api/user_test.go +++ b/backend/tests/api/user_test.go @@ -2,95 +2,116 @@ package tests import ( "fmt" - - stdliberrors "errors" "net/http" "testing" + stdliberrors "errors" + "github.com/GenerateNU/sac/backend/src/auth" "github.com/GenerateNU/sac/backend/src/errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/transactions" + h "github.com/GenerateNU/sac/backend/tests/api/helpers" "github.com/gofiber/fiber/v2" + "github.com/google/uuid" "gorm.io/gorm" "github.com/goccy/go-json" - "github.com/huandu/go-assert" ) -func TestGetAllUsersWorks(t *testing.T) { - TestRequest{ - Method: fiber.MethodGet, - Path: "/api/v1/users/", - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { +func TestGetUsersWorksForSuper(t *testing.T) { + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/", + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { var users []models.User err := json.NewDecoder(resp.Body).Decode(&users) - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(1, len(users)) + eaa.Assert.Equal(1, len(users)) respUser := users[0] - assert.Equal("SAC", respUser.FirstName) - assert.Equal("Super", respUser.LastName) - assert.Equal("generatesac@gmail.com", respUser.Email) - assert.Equal("000000000", respUser.NUID) - assert.Equal(models.College("KCCS"), respUser.College) - assert.Equal(models.Year(1), respUser.Year) + eaa.Assert.Equal("SAC", respUser.FirstName) + eaa.Assert.Equal("Super", respUser.LastName) + eaa.Assert.Equal("generatesac@gmail.com", respUser.Email) + eaa.Assert.Equal("000000000", respUser.NUID) + eaa.Assert.Equal(models.College("KCCS"), respUser.College) + eaa.Assert.Equal(models.Year(1), respUser.Year) - dbUsers, err := transactions.GetAllUsers(app.Conn) + dbUsers, err := transactions.GetUsers(eaa.App.Conn, 1, 0) - assert.NilError(&err) + eaa.Assert.NilError(&err) - assert.Equal(1, len(dbUsers)) + eaa.Assert.Equal(1, len(dbUsers)) dbUser := dbUsers[0] - assert.Equal(dbUser, respUser) + eaa.Assert.Equal(dbUser, respUser) }, }, ).Close() } +func TestGetUsersFailsForStudent(t *testing.T) { + h.InitTest(t).TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/", + Role: &models.Student, + }, + errors.Unauthorized, + ).Close() +} + func TestGetUserWorks(t *testing.T) { - id := 1 - - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%d", id), - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: "/api/v1/users/:userID", + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { var respUser models.User err := json.NewDecoder(resp.Body).Decode(&respUser) - assert.NilError(err) + eaa.Assert.NilError(err) + + sampleStudent, rawPassword := h.SampleStudentFactory() - assert.Equal("SAC", respUser.FirstName) - assert.Equal("Super", respUser.LastName) - assert.Equal("generatesac@gmail.com", respUser.Email) - assert.Equal("000000000", respUser.NUID) - assert.Equal(models.College("KCCS"), respUser.College) - assert.Equal(models.Year(1), respUser.Year) + sampleUser := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) - dbUser, err := transactions.GetUser(app.Conn, uint(id)) + eaa.Assert.Equal(sampleUser["first_name"].(string), respUser.FirstName) + eaa.Assert.Equal(sampleUser["last_name"].(string), respUser.LastName) + eaa.Assert.Equal(sampleUser["email"].(string), respUser.Email) + eaa.Assert.Equal(sampleUser["nuid"].(string), respUser.NUID) + eaa.Assert.Equal(models.College(sampleUser["college"].(string)), respUser.College) + eaa.Assert.Equal(models.Year(sampleUser["year"].(int)), respUser.Year) - assert.NilError(&err) + dbUser, err := transactions.GetUser(eaa.App.Conn, eaa.App.TestUser.UUID) - assert.Equal(dbUser, &respUser) + eaa.Assert.NilError(&err) + + eaa.Assert.Equal(dbUser, &respUser) }, }, ).Close() } func TestGetUserFailsBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + badRequests := []string{ "0", "-1", @@ -100,115 +121,115 @@ func TestGetUserFailsBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%s", badRequest), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateID, + appAssert.TestOnError( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s", badRequest), + Role: &models.Super, }, - ).Close() + errors.FailedToValidateID, + ) } + + appAssert.Close() } func TestGetUserFailsNotExist(t *testing.T) { - id := uint(69) - - TestRequest{ - Method: fiber.MethodGet, - Path: fmt.Sprintf("/api/v1/users/%d", id), - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 404, - Message: errors.UserNotFound, - }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { - var user models.User + uuid := uuid.New() - err := app.Conn.Where("id = ?", id).First(&user).Error + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/users/%s", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + var user models.User - assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) }, }, ).Close() } func TestUpdateUserWorks(t *testing.T) { - appAssert := CreateSampleUser(t) - - id := 2 newFirstName := "Michael" newLastName := "Brennan" - TestRequest{ - Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/users/%d", id), - Body: &map[string]interface{}{ - "first_name": newFirstName, - "last_name": newLastName, + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: "/api/v1/users/:userID", + Body: &map[string]interface{}{ + "first_name": newFirstName, + "last_name": newLastName, + }, + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), }, - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: 200, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + h.TesterWithStatus{ + Status: fiber.StatusOK, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { var respUser models.User err := json.NewDecoder(resp.Body).Decode(&respUser) - assert.NilError(err) + eaa.Assert.NilError(err) + + sampleStudent, rawPassword := h.SampleStudentFactory() + + sampleStudentJSON := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) - assert.Equal(newFirstName, respUser.FirstName) - assert.Equal(newLastName, respUser.LastName) - assert.Equal((*SampleUserFactory())["email"].(string), respUser.Email) - assert.Equal((*SampleUserFactory())["nuid"].(string), respUser.NUID) - assert.Equal(models.College((*SampleUserFactory())["college"].(string)), respUser.College) - assert.Equal(models.Year((*SampleUserFactory())["year"].(int)), respUser.Year) + eaa.Assert.Equal(newFirstName, respUser.FirstName) + eaa.Assert.Equal(newLastName, respUser.LastName) + eaa.Assert.Equal((sampleStudentJSON)["email"].(string), respUser.Email) + eaa.Assert.Equal((sampleStudentJSON)["nuid"].(string), respUser.NUID) + eaa.Assert.Equal(models.College((sampleStudentJSON)["college"].(string)), respUser.College) + eaa.Assert.Equal(models.Year((sampleStudentJSON)["year"].(int)), respUser.Year) var dbUser models.User - err = app.Conn.First(&dbUser, id).Error + err = eaa.App.Conn.First(&dbUser, eaa.App.TestUser.UUID).Error - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(dbUser.FirstName, respUser.FirstName) - assert.Equal(dbUser.LastName, respUser.LastName) - assert.Equal(dbUser.Email, respUser.Email) - assert.Equal(dbUser.NUID, respUser.NUID) - assert.Equal(dbUser.College, respUser.College) - assert.Equal(dbUser.Year, respUser.Year) + eaa.Assert.Equal(dbUser.FirstName, respUser.FirstName) + eaa.Assert.Equal(dbUser.LastName, respUser.LastName) + eaa.Assert.Equal(dbUser.Email, respUser.Email) + eaa.Assert.Equal(dbUser.NUID, respUser.NUID) + eaa.Assert.Equal(dbUser.College, respUser.College) + eaa.Assert.Equal(dbUser.Year, respUser.Year) }, }, ).Close() } func TestUpdateUserFailsOnInvalidBody(t *testing.T) { - appAssert := CreateSampleUser(t) - for _, invalidData := range []map[string]interface{}{ {"email": "not.northeastern@gmail.com"}, {"nuid": "1800-123-4567"}, - {"password": "1234"}, {"year": 1963}, {"college": "UT-Austin"}, } { - TestRequest{ - Method: fiber.MethodPatch, - Path: "/api/v1/users/2", - Body: &invalidData, - }.TestOnStatusMessageAndDB(t, &appAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateUser, - }, - DBTester: TestNumUsersRemainsAt2, + invalidData := invalidData + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: "/api/v1/users/:userID", + Body: &invalidData, + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), }, - ) + h.ErrorWithTester{ + Error: errors.FailedToValidateUser, + Tester: TestNumUsersRemainsAt2, + }, + ).Close() } - appAssert.Close() } func TestUpdateUserFailsBadRequest(t *testing.T) { @@ -220,82 +241,88 @@ func TestUpdateUserFailsBadRequest(t *testing.T) { "null", } + sampleStudent, rawPassword := h.SampleStudentFactory() + slightlyDifferentSampleStudentJSON := h.SampleStudentJSONFactory(sampleStudent, rawPassword) + (*slightlyDifferentSampleStudentJSON)["first_name"] = "John" + for _, badRequest := range badRequests { - TestRequest{ + h.InitTest(t).TestOnError(h.TestRequest{ Method: fiber.MethodPatch, Path: fmt.Sprintf("/api/v1/users/%s", badRequest), - Body: SampleUserFactory(), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateID, - }, + Body: slightlyDifferentSampleStudentJSON, + Role: &models.Student, + }, + errors.FailedToValidateID, ).Close() } } func TestUpdateUserFailsOnIdNotExist(t *testing.T) { - id := uint(69) - - TestRequest{ - Method: fiber.MethodPatch, - Path: fmt.Sprintf("/api/v1/users/%d", id), - Body: SampleUserFactory(), - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 404, - Message: errors.UserNotFound, - }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + uuid := uuid.New() + + sampleStudent, rawPassword := h.SampleStudentFactory() + + h.InitTest(t).TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPatch, + Path: fmt.Sprintf("/api/v1/users/%s", uuid), + Body: h.SampleStudentJSONFactory(sampleStudent, rawPassword), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { var user models.User - err := app.Conn.Where("id = ?", id).First(&user).Error + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error - assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) }, }, ).Close() } func TestDeleteUserWorks(t *testing.T) { - appAssert := CreateSampleUser(t) - - TestRequest{ - Method: fiber.MethodDelete, - Path: "/api/v1/users/2", - }.TestOnStatusAndDB(t, &appAssert, - DBTesterWithStatus{ - Status: 204, - DBTester: TestNumUsersRemainsAt1, + h.InitTest(t).TestOnStatusAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: "/api/v1/users/:userID", + Role: &models.Student, + TestUserIDReplaces: h.StringToPointer(":userID"), + }, + h.TesterWithStatus{ + Status: fiber.StatusNoContent, + Tester: TestNumUsersRemainsAt1, }, ).Close() } func TestDeleteUserNotExist(t *testing.T) { - id := uint(69) + uuid := uuid.New() - TestRequest{ + h.InitTest(t).TestOnErrorAndTester(h.TestRequest{ Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/users/%d", id), - }.TestOnStatusMessageAndDB(t, nil, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 404, - Message: errors.UserNotFound, - }, - DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + Path: fmt.Sprintf("/api/v1/users/%s", uuid), + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserNotFound, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { var user models.User - err := app.Conn.Where("id = ?", id).First(&user).Error + err := eaa.App.Conn.Where("id = ?", uuid).First(&user).Error + + eaa.Assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) - assert.Assert(stdliberrors.Is(err, gorm.ErrRecordNotFound)) + TestNumUsersRemainsAt1(eaa, resp) }, }, ).Close() } func TestDeleteUserBadRequest(t *testing.T) { + appAssert := h.InitTest(t) + badRequests := []string{ "0", "-1", @@ -305,173 +332,185 @@ func TestDeleteUserBadRequest(t *testing.T) { } for _, badRequest := range badRequests { - TestRequest{ - Method: fiber.MethodDelete, - Path: fmt.Sprintf("/api/v1/users/%s", badRequest), - }.TestOnStatusAndMessage(t, nil, - MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateID, + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodDelete, + Path: fmt.Sprintf("/api/v1/users/%s", badRequest), + Role: &models.Super, }, - ).Close() + h.ErrorWithTester{ + Error: errors.FailedToValidateID, + Tester: TestNumUsersRemainsAt1, + }, + ) } -} - -func SampleUserFactory() *map[string]interface{} { - return &map[string]interface{}{ - "first_name": "Jane", - "last_name": "Doe", - "email": "doe.jane@northeastern.edu", - "password": "1234567890&", - "nuid": "001234567", - "college": "KCCS", - "year": 3, - } + appAssert.Close() } -func AssertUserWithIDBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, id uint, body *map[string]interface{}) { +func AssertUserWithIDBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response, body *map[string]interface{}) uuid.UUID { var respUser models.User err := json.NewDecoder(resp.Body).Decode(&respUser) - assert.NilError(err) + eaa.Assert.NilError(err) + + var dbUsers []models.User - var dbUser models.User + err = eaa.App.Conn.Find(&dbUsers).Error - err = app.Conn.First(&dbUser, id).Error + eaa.Assert.NilError(err) - assert.NilError(err) + eaa.Assert.Equal(2, len(dbUsers)) - assert.Equal(dbUser.FirstName, respUser.FirstName) - assert.Equal(dbUser.LastName, respUser.LastName) - assert.Equal(dbUser.Email, respUser.Email) - assert.Equal(dbUser.NUID, respUser.NUID) - assert.Equal(dbUser.College, respUser.College) - assert.Equal(dbUser.Year, respUser.Year) + dbUser := dbUsers[1] + + eaa.Assert.Equal(dbUser.FirstName, respUser.FirstName) + eaa.Assert.Equal(dbUser.LastName, respUser.LastName) + eaa.Assert.Equal(dbUser.Email, respUser.Email) + eaa.Assert.Equal(dbUser.NUID, respUser.NUID) + eaa.Assert.Equal(dbUser.College, respUser.College) + eaa.Assert.Equal(dbUser.Year, respUser.Year) match, err := auth.ComparePasswordAndHash((*body)["password"].(string), dbUser.PasswordHash) - assert.NilError(err) + eaa.Assert.NilError(err) + + eaa.Assert.Assert(match) - assert.Assert(match) + eaa.Assert.Equal((*body)["first_name"].(string), dbUser.FirstName) + eaa.Assert.Equal((*body)["last_name"].(string), dbUser.LastName) + eaa.Assert.Equal((*body)["email"].(string), dbUser.Email) + eaa.Assert.Equal((*body)["nuid"].(string), dbUser.NUID) + eaa.Assert.Equal(models.College((*body)["college"].(string)), dbUser.College) + eaa.Assert.Equal(models.Year((*body)["year"].(int)), dbUser.Year) - assert.Equal((*body)["first_name"].(string), dbUser.FirstName) - assert.Equal((*body)["last_name"].(string), dbUser.LastName) - assert.Equal((*body)["email"].(string), dbUser.Email) - assert.Equal((*body)["nuid"].(string), dbUser.NUID) - assert.Equal(models.College((*body)["college"].(string)), dbUser.College) - assert.Equal(models.Year((*body)["year"].(int)), dbUser.Year) + return dbUser.ID } -func AssertSampleUserBodyRespDB(app TestApp, assert *assert.A, resp *http.Response) { - AssertUserWithIDBodyRespDB(app, assert, resp, 2, SampleUserFactory()) +func AssertSampleUserBodyRespDB(eaa h.ExistingAppAssert, resp *http.Response) uuid.UUID { + sampleStudent, rawPassword := h.SampleStudentFactory() + + return AssertUserWithIDBodyRespDB(eaa, resp, h.SampleStudentJSONFactory(sampleStudent, rawPassword)) } -func CreateSampleUser(t *testing.T) ExistingAppAssert { - return TestRequest{ +func CreateSampleStudent(t *testing.T, existingAppAssert *h.ExistingAppAssert) (h.ExistingAppAssert, uuid.UUID, *map[string]interface{}) { + if existingAppAssert == nil { + newAppAssert := h.InitTest(t) + existingAppAssert = &newAppAssert + } + + var uuid uuid.UUID + + sampleStudent, rawPassword := h.SampleStudentFactory() + + existingAppAssert.TestOnStatusAndTester(h.TestRequest{ Method: fiber.MethodPost, Path: "/api/v1/users/", - Body: SampleUserFactory(), - }.TestOnStatusAndDB(t, nil, - DBTesterWithStatus{ - Status: 201, - DBTester: AssertSampleUserBodyRespDB, + Body: h.SampleStudentJSONFactory(sampleStudent, rawPassword), + Role: &models.Super, + }, + h.TesterWithStatus{ + Status: fiber.StatusCreated, + Tester: func(eaa h.ExistingAppAssert, resp *http.Response) { + uuid = AssertSampleUserBodyRespDB(eaa, resp) + }, }, ) + + return *existingAppAssert, uuid, h.SampleStudentJSONFactory(sampleStudent, rawPassword) } -func AssertNumUsersRemainsAtN(app TestApp, assert *assert.A, resp *http.Response, n int) { +func AssertNumUsersRemainsAtN(eaa h.ExistingAppAssert, resp *http.Response, n int) { var users []models.User - err := app.Conn.Find(&users).Error + err := eaa.App.Conn.Find(&users).Error - assert.NilError(err) + eaa.Assert.NilError(err) - assert.Equal(n, len(users)) + eaa.Assert.Equal(n, len(users)) } -var TestNumUsersRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertNumUsersRemainsAtN(app, assert, resp, 1) +var TestNumUsersRemainsAt1 = func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumUsersRemainsAtN(eaa, resp, 1) } -var TestNumUsersRemainsAt2 = func(app TestApp, assert *assert.A, resp *http.Response) { - AssertNumUsersRemainsAtN(app, assert, resp, 2) +var TestNumUsersRemainsAt2 = func(eaa h.ExistingAppAssert, resp *http.Response) { + AssertNumUsersRemainsAtN(eaa, resp, 2) } func TestCreateUserWorks(t *testing.T) { - CreateSampleUser(t).Close() + appAssert, _, _ := CreateSampleStudent(t, nil) + appAssert.Close() } func TestCreateUserFailsIfUserWithEmailAlreadyExists(t *testing.T) { - appAssert := CreateSampleUser(t) + appAssert, studentUUID, body := CreateSampleStudent(t, nil) - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/users/", - Body: SampleUserFactory(), - }.TestOnStatusMessageAndDB(t, &appAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.UserAlreadyExists, - }, - DBTester: TestNumUsersRemainsAt2, - }, - ) + (*body)["id"] = studentUUID - appAssert.Close() + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/", + Body: body, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserAlreadyExists, + Tester: TestNumUsersRemainsAt2, + }, + ).Close() } func TestCreateUserFailsIfUserWithNUIDAlreadyExists(t *testing.T) { - appAssert := CreateSampleUser(t) - - slightlyDifferentSampleUser := &map[string]interface{}{ - "first_name": "John", - "last_name": "Doe", - "email": "doe.john@northeastern.edu", - "password": "1234567890&", - "nuid": "001234567", - "college": "KCCS", - "year": 3, - } + appAssert, _, _ := CreateSampleStudent(t, nil) - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/users/", - Body: slightlyDifferentSampleUser, - }.TestOnStatusMessageAndDB(t, &appAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.UserAlreadyExists, - }, - DBTester: TestNumUsersRemainsAt2, + sampleStudent, rawPassword := h.SampleStudentFactory() + + slightlyDifferentSampleStudentJSON := h.SampleStudentJSONFactory(sampleStudent, rawPassword) + + (*slightlyDifferentSampleStudentJSON)["first_name"] = "John" + (*slightlyDifferentSampleStudentJSON)["last_name"] = "Doe" + (*slightlyDifferentSampleStudentJSON)["email"] = "doe.john@northeastern.edu" + + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/", + Body: slightlyDifferentSampleStudentJSON, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.UserAlreadyExists, + Tester: TestNumUsersRemainsAt2, }, ).Close() } func AssertCreateBadDataFails(t *testing.T, jsonKey string, badValues []interface{}) { - appAssert := CreateSampleUser(t) + appAssert, _, _ := CreateSampleStudent(t, nil) + + sampleStudent, rawPassword := h.SampleStudentFactory() for _, badValue := range badValues { - sampleUserPermutation := *SampleUserFactory() + sampleUserPermutation := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) sampleUserPermutation[jsonKey] = badValue - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/users/", - Body: &sampleUserPermutation, - }.TestOnStatusMessageAndDB(t, &appAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateUser, - }, - DBTester: TestNumUsersRemainsAt2, + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/", + Body: &sampleUserPermutation, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateUser, + Tester: TestNumUsersRemainsAt2, }, ) } + appAssert.Close() } @@ -522,13 +561,12 @@ func TestCreateUserFailsOnInvalidYear(t *testing.T) { func TestCreateUserFailsOnInvalidCollege(t *testing.T) { khouryAbbreviation := "KCCS" - permutations := AllCasingPermutations(khouryAbbreviation) + permutations := h.AllCasingPermutations(khouryAbbreviation) permutationsWithoutKhoury := make([]interface{}, len(permutations)-1) for _, permutation := range permutations { if permutation != khouryAbbreviation { permutationsWithoutKhoury = append(permutationsWithoutKhoury, permutation) } - } AssertCreateBadDataFails(t, @@ -537,7 +575,9 @@ func TestCreateUserFailsOnInvalidCollege(t *testing.T) { } func TestCreateUserFailsOnMissingFields(t *testing.T) { - appAssert := CreateSampleUser(t) + appAssert, _, _ := CreateSampleStudent(t, nil) + + sampleStudent, rawPassword := h.SampleStudentFactory() for _, missingField := range []string{ "first_name", @@ -548,22 +588,22 @@ func TestCreateUserFailsOnMissingFields(t *testing.T) { "college", "year", } { - sampleUserPermutation := *SampleUserFactory() + sampleUserPermutation := *h.SampleStudentJSONFactory(sampleStudent, rawPassword) delete(sampleUserPermutation, missingField) - TestRequest{ - Method: fiber.MethodPost, - Path: "/api/v1/users/", - Body: &sampleUserPermutation, - }.TestOnStatusMessageAndDB(t, &appAssert, - StatusMessageDBTester{ - MessageWithStatus: MessageWithStatus{ - Status: 400, - Message: errors.FailedToValidateUser, - }, - DBTester: TestNumUsersRemainsAt2, + appAssert.TestOnErrorAndTester( + h.TestRequest{ + Method: fiber.MethodPost, + Path: "/api/v1/users/", + Body: &sampleUserPermutation, + Role: &models.Super, + }, + h.ErrorWithTester{ + Error: errors.FailedToValidateUser, + Tester: TestNumUsersRemainsAt2, }, ) } + appAssert.Close() } diff --git a/backend/tests/auth_test.go b/backend/tests/auth_test.go new file mode 100644 index 000000000..3bfc1f530 --- /dev/null +++ b/backend/tests/auth_test.go @@ -0,0 +1,182 @@ +package tests + +import ( + "testing" + + "github.com/GenerateNU/sac/backend/src/auth" + "github.com/GenerateNU/sac/backend/src/config" + + m "github.com/garrettladley/mattress" + "github.com/golang-jwt/jwt" + "github.com/huandu/go-assert" +) + +func AuthSettings() (*config.AuthSettings, error) { + accessKey, err := m.NewSecret("g(r|##*?>\\Qp}h37e+,T2") + if err != nil { + return nil, err + } + + refreshKey, err := m.NewSecret("amk*2!gG}1i\"8D9RwJS$p") + if err != nil { + return nil, err + } + + return &config.AuthSettings{ + AccessKey: accessKey, + AccessTokenExpiry: 60, + RefreshKey: refreshKey, + RefreshTokenExpiry: 30, + }, nil +} + +func TestCreateTokenPairSuccess(t *testing.T) { + assert := assert.New(t) + + id := "user123" + role := "admin" + + authSettings, err := AuthSettings() + assert.NilError(err) + + accessToken, refreshToken, authErr := auth.CreateTokenPair(id, role, *authSettings) + + assert.Assert(authErr == nil) + + assert.Assert(accessToken != nil) + assert.Assert(refreshToken != nil) +} + +func TestCreateTokenPairFailure(t *testing.T) { + assert := assert.New(t) + + id := "user123" + role := "" + + authSettings, err := AuthSettings() + + assert.NilError(err) + + accessToken, refreshToken, authErr := auth.CreateTokenPair(id, role, *authSettings) + + assert.Assert(authErr != nil) + + assert.Assert(accessToken == nil) + assert.Assert(refreshToken == nil) +} + +func TestCreateAccessTokenSuccess(t *testing.T) { + assert := assert.New(t) + + id := "user123" + role := "admin" + + authSettings, err := AuthSettings() + + assert.NilError(err) + + accessToken, authErr := auth.CreateAccessToken(id, role, authSettings.AccessTokenExpiry, authSettings.AccessKey) + + assert.Assert(authErr == nil) + + assert.Assert(accessToken != nil) +} + +func TestCreateAccessTokenFailure(t *testing.T) { + assert := assert.New(t) + + id := "user123" + role := "" + + authSettings, err := AuthSettings() + + assert.NilError(err) + + accessToken, authErr := auth.CreateAccessToken(id, role, authSettings.AccessTokenExpiry, authSettings.AccessKey) + + assert.Assert(authErr != nil) + + assert.Assert(accessToken == nil) +} + +func TestCreateRefreshTokenSuccess(t *testing.T) { + assert := assert.New(t) + + id := "user123" + + authSettings, err := AuthSettings() + + assert.NilError(err) + + refreshToken, authErr := auth.CreateRefreshToken(id, authSettings.RefreshTokenExpiry, authSettings.RefreshKey) + + assert.Assert(authErr == nil) + + assert.Assert(refreshToken != nil) +} + +func TestCreateRefreshTokenFailure(t *testing.T) { + assert := assert.New(t) + + id := "" + + authSettings, err := AuthSettings() + + assert.NilError(err) + + refreshToken, authErr := auth.CreateRefreshToken(id, authSettings.RefreshTokenExpiry, authSettings.RefreshKey) + + assert.Assert(authErr != nil) + + assert.Assert(refreshToken == nil) +} + +func TestSignTokenSuccess(t *testing.T) { + assert := assert.New(t) + + token := jwt.New(jwt.SigningMethodHS256) + + assert.Assert(token != nil) + + token.Claims = jwt.MapClaims{ + "sub": "user123", + "exp": 1234567890, + "iat": 1234567890, + "iss": "sac", + } + + key, err := m.NewSecret("secret") + + assert.NilError(err) + + signedToken, authErr := auth.SignToken(token, key) + + assert.Assert(authErr == nil) + + assert.Assert(signedToken != nil) +} + +func TestSignTokenFailure(t *testing.T) { + assert := assert.New(t) + + token := jwt.New(jwt.SigningMethodHS256) + + assert.Assert(token != nil) + + token.Claims = jwt.MapClaims{ + "sub": "user123", + "exp": 1234567890, + "iat": 1234567890, + "iss": "sac", + } + + key, err := m.NewSecret("") + + assert.NilError(err) + + signedToken, authErr := auth.SignToken(token, key) + + assert.Assert(authErr != nil) + + assert.Assert(signedToken == nil) +} diff --git a/backend/tests/utilities_test.go b/backend/tests/utilities_test.go deleted file mode 100644 index d7ed0bf33..000000000 --- a/backend/tests/utilities_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/GenerateNU/sac/backend/src/utilities" - - "github.com/huandu/go-assert" -) - -func TestThatContainsWorks(t *testing.T) { - assert := assert.New(t) - - slice := []string{"foo", "bar", "baz"} - - assert.Assert(utilities.Contains(slice, "foo")) - assert.Assert(utilities.Contains(slice, "bar")) - assert.Assert(utilities.Contains(slice, "baz")) - assert.Assert(!utilities.Contains(slice, "qux")) -} diff --git a/cli/.golangci.yml b/cli/.golangci.yml new file mode 100644 index 000000000..31fd57a91 --- /dev/null +++ b/cli/.golangci.yml @@ -0,0 +1,17 @@ +linters: + enable: + - cyclop + - exportloopref + - gocritic + - gosec + - ineffassign + - misspell + - prealloc + - unconvert + - unparam + - goimports + - whitespace + +linters-settings: + whitespace: + multi-func: true diff --git a/cli/commands/be.go b/cli/commands/be.go new file mode 100644 index 000000000..88a4c01b7 --- /dev/null +++ b/cli/commands/be.go @@ -0,0 +1,48 @@ +package commands + +import ( + "fmt" + "os" + "os/exec" + + _ "github.com/lib/pq" + "github.com/urfave/cli/v2" +) + +func RunBackendCommand() *cli.Command { + command := cli.Command{ + Name: "be", + Usage: "Run the backend", + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + err := RunBE() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, + } + + return &command +} + +func RunBE() error { + goCmd := exec.Command("go", "run", "main.go") + goCmd.Dir = BACKEND_DIR + + goCmd.Stdout = os.Stdout + goCmd.Stderr = os.Stderr + + fmt.Println("Running backend") + + err := goCmd.Run() + if err != nil { + return fmt.Errorf("error running backend: %w", err) + } + + return nil +} diff --git a/cli/commands/clean_tests.go b/cli/commands/clean_tests.go index a4c3ad3c6..e3d4c6973 100644 --- a/cli/commands/clean_tests.go +++ b/cli/commands/clean_tests.go @@ -1,22 +1,31 @@ package commands import ( + "database/sql" "fmt" - "os/exec" + "os/user" + "sync" + _ "github.com/lib/pq" "github.com/urfave/cli/v2" ) func ClearDBCommand() *cli.Command { command := cli.Command{ - Name: "clean", - Usage: "Remove databases used for testing", + Name: "clean", + Category: "Database Operations", + Aliases: []string{"c"}, + Usage: "Remove databases used for testing", Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) } - ClearDB() + err := CleanTestDBs() + if err != nil { + return cli.Exit(err.Error(), 1) + } + return nil }, } @@ -24,20 +33,55 @@ func ClearDBCommand() *cli.Command { return &command } -func ClearDB() error { +func CleanTestDBs() error { + fmt.Println("Cleaning test databases") + + db, err := sql.Open("postgres", CONFIG.Database.WithDb()) + if err != nil { + return err + } - fmt.Println("Clearing databases") + defer db.Close() - cmd := exec.Command("./scripts/clean_old_test_dbs.sh") - cmd.Dir = ROOT_DIR + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("failed to get current user: %w", err) + } - out, err := cmd.CombinedOutput() + rows, err := db.Query("SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres' AND datname != $1 AND datname != $2 AND datname LIKE 'sac_test_%';", currentUser.Username, CONFIG.Database.DatabaseName) if err != nil { - fmt.Println(string(out)) - return cli.Exit("Failed to clean old test databases", 1) + return err + } + + defer rows.Close() + + var wg sync.WaitGroup + + for rows.Next() { + var dbName string + + if err := rows.Scan(&dbName); err != nil { + return err + } + + wg.Add(1) + + go func(dbName string) { + defer wg.Done() + + fmt.Printf("Dropping database %s\n", dbName) + + if _, err := db.Exec(fmt.Sprintf("DROP DATABASE %s", dbName)); err != nil { + fmt.Printf("Failed to drop database %s: %v\n", dbName, err) + } + }(dbName) } - fmt.Println("Databases cleared") - + if err := rows.Err(); err != nil { + return err + } + + wg.Wait() + return nil } diff --git a/cli/commands/config.go b/cli/commands/config.go index 06ca14e6b..203219218 100644 --- a/cli/commands/config.go +++ b/cli/commands/config.go @@ -3,9 +3,14 @@ package commands import ( "path/filepath" + "github.com/GenerateNU/sac/backend/src/config" "github.com/GenerateNU/sac/cli/utils" ) -var ROOT_DIR, _ = utils.GetRootDir() -var FRONTEND_DIR = filepath.Join(ROOT_DIR, "/frontend") -var BACKEND_DIR = filepath.Join(ROOT_DIR, "/backend/src") +var ( + ROOT_DIR, _ = utils.GetRootDir() + FRONTEND_DIR = filepath.Join(ROOT_DIR, "/frontend") + BACKEND_DIR = filepath.Join(ROOT_DIR, "/backend/src") + CONFIG, _ = config.GetConfiguration(filepath.Join(ROOT_DIR, "/config")) + MIGRATION_FILE = filepath.Join(BACKEND_DIR, "/migrations/data.sql") +) diff --git a/cli/commands/drop.go b/cli/commands/drop.go index 527287d3d..3a34f1ae1 100644 --- a/cli/commands/drop.go +++ b/cli/commands/drop.go @@ -1,22 +1,44 @@ package commands import ( + "database/sql" "fmt" - "os/exec" + "sync" "github.com/urfave/cli/v2" ) -func DropDBCommand() *cli.Command { +var dbMutex sync.Mutex + +func DropCommand() *cli.Command { command := cli.Command{ - Name: "drop", - Usage: "Drops the database", + Name: "drop", + Aliases: []string{"d"}, + Usage: "Drop data with a migration or drops the entire database", + Category: "Database Operations", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "data", + Usage: "Drop only data, not the entire database", + }, + }, Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) } - DropDB() + if c.Bool("data") { + err := DropData() + if err != nil { + return cli.Exit(err.Error(), 1) + } + } else { + err := DropDB() + if err != nil { + return cli.Exit(err.Error(), 1) + } + } + return nil }, } @@ -24,20 +46,103 @@ func DropDBCommand() *cli.Command { return &command } +func DropData() error { + fmt.Println("Clearing database") + + dbMutex.Lock() + defer dbMutex.Unlock() + + db, err := sql.Open("postgres", CONFIG.Database.WithDb()) + if err != nil { + return err + } + defer db.Close() + + rows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'") + if err != nil { + return fmt.Errorf("error retrieving tables: %w", err) + } + defer rows.Close() + + for rows.Next() { + var tablename string + if err := rows.Scan(&tablename); err != nil { + return fmt.Errorf("error scanning table name: %w", err) + } + + _, err := db.Exec("DELETE FROM $1", tablename) + if err != nil { + return fmt.Errorf("error deleting rows from table %s: %w", tablename, err) + } + fmt.Printf("Removed all rows from table %s\n", tablename) + } + + if err := rows.Err(); err != nil { + return fmt.Errorf("error in rows handling: %w", err) + } + + if Migrate() != nil { + return fmt.Errorf("error migrating database: %w", err) + } + + fmt.Println("All rows removed successfully.") + return nil +} + func DropDB() error { - fmt.Println("Droping database") + fmt.Println("Dropping database") + + dbMutex.Lock() + defer dbMutex.Unlock() + + db, err := sql.Open("postgres", CONFIG.Database.WithDb()) + if err != nil { + return err + } + + defer db.Close() - cmd := exec.Command("../../scripts/drop_db.sh") - cmd.Dir = BACKEND_DIR + var tableCount int - output, err := cmd.CombinedOutput() + err = db.QueryRow("SELECT COUNT(*) FROM pg_tables WHERE schemaname = 'public'").Scan(&tableCount) if err != nil { - return cli.Exit("Error running drop_db.sh", 1) + return fmt.Errorf("error checking tables: %w", err) } - fmt.Println(string(output)) + if tableCount == 0 { + fmt.Println("No tables to drop. The database is empty.") + return nil + } - fmt.Println("Done droping database") + fmt.Println("Generating DROP TABLE statements...") + rows, err := db.Query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'") + if err != nil { + return fmt.Errorf("error generating DROP TABLE statements: %w", err) + } + + defer rows.Close() + + fmt.Println("Dropping tables...") + + for rows.Next() { + var tablename string + if err := rows.Scan(&tablename); err != nil { + return fmt.Errorf("error reading table name: %w", err) + } + + dropStmt := fmt.Sprintf("DROP TABLE IF EXISTS \"%s\" CASCADE", tablename) + if _, err := db.Exec(dropStmt); err != nil { + fmt.Printf("Error dropping table %s: %v\n", tablename, err) + } else { + fmt.Printf("Dropped table %s\n", tablename) + } + } + + if err := rows.Err(); err != nil { + return fmt.Errorf("error in rows handling: %w", err) + } + + fmt.Println("All tables dropped successfully.") return nil -} \ No newline at end of file +} diff --git a/cli/commands/format.go b/cli/commands/format.go index 694e3fe57..cfaa9ddea 100644 --- a/cli/commands/format.go +++ b/cli/commands/format.go @@ -10,8 +10,9 @@ import ( func FormatCommand() *cli.Command { command := cli.Command{ - Name: "format", - Usage: "Runs formatting tools", + Name: "format", + Usage: "Runs formatting tools", + Aliases: []string{"f"}, Flags: []cli.Flag{ &cli.StringFlag{ Name: "frontend", @@ -38,7 +39,10 @@ func FormatCommand() *cli.Command { runFrontend := folder != "" runBackend := c.Bool("backend") - Format(folder, runFrontend, runBackend) + err := Format(folder, runFrontend, runBackend) + if err != nil { + return cli.Exit(err.Error(), 1) + } return nil }, @@ -55,7 +59,10 @@ func Format(folder string, runFrontend bool, runBackend bool) error { wg.Add(1) go func() { defer wg.Done() - BackendFormat() + err := BackendFormat() + if err != nil { + fmt.Println(err) + } }() } @@ -64,7 +71,10 @@ func Format(folder string, runFrontend bool, runBackend bool) error { wg.Add(1) go func() { defer wg.Done() - FrontendFormat(folder) + err := FrontendFormat(folder) + if err != nil { + fmt.Println(err) + } }() } @@ -76,7 +86,7 @@ func Format(folder string, runFrontend bool, runBackend bool) error { func BackendFormat() error { fmt.Println("Formatting backend") - cmd := exec.Command("go", "fmt", "./...") + cmd := exec.Command("gofumpt", "-l", "-w", ".") cmd.Dir = BACKEND_DIR err := cmd.Run() @@ -90,5 +100,5 @@ func BackendFormat() error { func FrontendFormat(folder string) error { fmt.Println("UNIMPLEMENTED") - return nil -} \ No newline at end of file + return nil +} diff --git a/cli/commands/insert.go b/cli/commands/insert.go new file mode 100644 index 000000000..9b8a55754 --- /dev/null +++ b/cli/commands/insert.go @@ -0,0 +1,84 @@ +package commands + +import ( + "database/sql" + "fmt" + "os" + "os/exec" + + "github.com/lib/pq" + "github.com/urfave/cli/v2" +) + +func InsertCommand() *cli.Command { + command := cli.Command{ + Name: "insert", + Category: "Database Operations", + Aliases: []string{"i"}, + Usage: "Inserts mock data into the database", + Action: func(c *cli.Context) error { + if c.Args().Len() > 0 { + return cli.Exit("Invalid arguments", 1) + } + + err := InsertDB() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + return nil + }, + } + + return &command +} + +func InsertDB() error { + db, err := sql.Open("postgres", CONFIG.Database.WithDb()) + if err != nil { + return err + } + + defer db.Close() + + var exists bool + + err = db.QueryRow("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' LIMIT 1);").Scan(&exists) + if err != nil { + return err + } + + if !exists { + fmt.Println("Database does not exist or has no tables. Running database migration.") + + migrateCmd := exec.Command("go", "run", "main.go", "--only-migrate") + + migrateCmd.Dir = BACKEND_DIR + + if err := migrateCmd.Run(); err != nil { + return fmt.Errorf("error running migration: %w", err) + } + } else { + fmt.Println("Database exists with tables.") + } + + migrationSQL, err := os.ReadFile(MIGRATION_FILE) + if err != nil { + return fmt.Errorf("error reading migration file: %w", err) + } + + _, err = db.Exec(string(migrationSQL)) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + fmt.Println("PostgreSQL Error:") + fmt.Println("Code:", pqErr.Code) + fmt.Println("Message:", pqErr.Message) + } else { + return fmt.Errorf("error executing migration: %w", err) + } + } + + fmt.Println("Data inserted successfully.") + + return nil +} diff --git a/cli/commands/lint.go b/cli/commands/lint.go index ea1680fb8..921a45eb8 100644 --- a/cli/commands/lint.go +++ b/cli/commands/lint.go @@ -10,8 +10,9 @@ import ( func LintCommand() *cli.Command { command := cli.Command{ - Name: "lint", - Usage: "Runs linting tools", + Name: "lint", + Aliases: []string{"l"}, + Usage: "Runs linting tools", Flags: []cli.Flag{ &cli.StringFlag{ Name: "frontend", @@ -38,7 +39,10 @@ func LintCommand() *cli.Command { runFrontend := folder != "" runBackend := c.Bool("backend") - Lint(folder, runFrontend, runBackend) + err := Lint(folder, runFrontend, runBackend) + if err != nil { + return cli.Exit(err.Error(), 1) + } return nil }, @@ -49,26 +53,45 @@ func LintCommand() *cli.Command { func Lint(folder string, runFrontend bool, runBackend bool) error { var wg sync.WaitGroup + errChan := make(chan error, 1) + var errOccurred bool // Flag to indicate whether an error has occurred // Start the backend if specified - if runBackend { + if runBackend && !errOccurred { wg.Add(1) go func() { defer wg.Done() - BackendLint() + err := BackendLint() + if err != nil { + errChan <- err + errOccurred = true + } }() } // Start the frontend if specified - if runFrontend { + if runFrontend && !errOccurred { wg.Add(1) go func() { defer wg.Done() - FrontendLint(folder) + err := FrontendLint(folder) + if err != nil { + errChan <- err + errOccurred = true + } }() } - wg.Wait() + go func() { + wg.Wait() + close(errChan) + }() + + for err := range errChan { + if err != nil { + return err + } + } return nil } @@ -76,7 +99,7 @@ func Lint(folder string, runFrontend bool, runBackend bool) error { func BackendLint() error { fmt.Println("Linting backend") - cmd := exec.Command("go", "vet", "./...") + cmd := exec.Command("golangci-lint", "run", "--fix") cmd.Dir = BACKEND_DIR err := cmd.Run() diff --git a/cli/commands/migrate.go b/cli/commands/migrate.go index 2ecc2b7a1..7aa7d8aa4 100644 --- a/cli/commands/migrate.go +++ b/cli/commands/migrate.go @@ -9,14 +9,18 @@ import ( func MigrateCommand() *cli.Command { command := cli.Command{ - Name: "migrate", - Usage: "Migrate the database", + Name: "migrate", + Usage: "Migrate the database, creating tables and relationships", + Category: "Database Operations", Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) } - Migrate() + err := Migrate() + if err != nil { + return cli.Exit(err.Error(), 1) + } return nil }, } @@ -37,17 +41,6 @@ func Migrate() error { fmt.Println(string(output)) - fmt.Println("Inserting data into database") - - scriptCmd := exec.Command("./scripts/insert_db.sh") - scriptCmd.Dir = ROOT_DIR - - output, err = scriptCmd.CombinedOutput() - if err != nil { - return cli.Exit("Error running insert_db.sh", 1) - } - - fmt.Println(string(output)) fmt.Println("Done migrating database") return nil diff --git a/cli/commands/reset.go b/cli/commands/reset.go index 44e16fd30..4e9cf4324 100644 --- a/cli/commands/reset.go +++ b/cli/commands/reset.go @@ -7,16 +7,35 @@ import ( "github.com/urfave/cli/v2" ) -func ResetDBCommand() *cli.Command { +func ResetCommand() *cli.Command { command := cli.Command{ - Name: "reset", - Usage: "Resets the database", + Name: "reset", + Aliases: []string{"r"}, + Usage: "Resets the database, dropping all tables, clearing data, and re-running migrations", + Category: "Database Operations", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "data", + Usage: "Reset only data, not the entire database, will re-run migrations", + }, + }, Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) } - ResetDB() + if c.Bool("data") { + err := ResetData() + if err != nil { + return cli.Exit(err.Error(), 1) + } + } else { + err := ResetMigration() + if err != nil { + return cli.Exit(err.Error(), 1) + } + } + return nil }, } @@ -24,22 +43,67 @@ func ResetDBCommand() *cli.Command { return &command } -func ResetDB() error { - fmt.Println("Resetting database") +func ResetData() error { + fmt.Println("Clearing database") + + err := DropData() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + cmd := exec.Command("sleep", "1") + cmd.Dir = BACKEND_DIR + + err = cmd.Run() + if err != nil { + return cli.Exit("Error running sleep", 1) + } + + err = Migrate() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + cmd = exec.Command("sleep", "1") + cmd.Dir = BACKEND_DIR + + err = cmd.Run() + if err != nil { + return cli.Exit("Error running sleep", 1) + } + + err = InsertDB() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + fmt.Println("Data reset successfully") + + return nil +} + +func ResetMigration() error { + fmt.Println("Resetting migration") - DropDB() + err := DropDB() + if err != nil { + return cli.Exit(err.Error(), 1) + } cmd := exec.Command("sleep", "1") - cmd.Dir = BACKEND_DIR + cmd.Dir = BACKEND_DIR - err := cmd.Run() + err = cmd.Run() if err != nil { return cli.Exit("Error running sleep", 1) } - Migrate() + err = Migrate() + if err != nil { + return cli.Exit(err.Error(), 1) + } + + fmt.Println("Migration reset successfully") - fmt.Println("Done resetting database") - return nil -} \ No newline at end of file +} diff --git a/cli/commands/swagger.go b/cli/commands/swagger.go index 467c58b2c..3881d93cf 100644 --- a/cli/commands/swagger.go +++ b/cli/commands/swagger.go @@ -9,14 +9,18 @@ import ( func SwaggerCommand() *cli.Command { command := cli.Command{ - Name: "swagger", - Usage: "Updates the swagger documentation", + Name: "swagger", + Aliases: []string{"swag"}, + Usage: "Updates the swagger documentation", Action: func(c *cli.Context) error { if c.Args().Len() > 0 { return cli.Exit("Invalid arguments", 1) } - Swagger() + err := Swagger() + if err != nil { + return cli.Exit(err.Error(), 1) + } return nil }, } diff --git a/cli/commands/test.go b/cli/commands/test.go index 50a58c934..be12543c4 100644 --- a/cli/commands/test.go +++ b/cli/commands/test.go @@ -10,8 +10,9 @@ import ( func TestCommand() *cli.Command { command := cli.Command{ - Name: "test", - Usage: "Runs tests", + Name: "test", + Aliases: []string{"t"}, + Usage: "Runs tests", Flags: []cli.Flag{ &cli.StringFlag{ Name: "frontend", @@ -37,50 +38,64 @@ func TestCommand() *cli.Command { folder := c.String("frontend") runFrontend := folder != "" runBackend := c.Bool("backend") - - Test(folder, runFrontend, runBackend) - + err := Test(folder, runFrontend, runBackend) + if err != nil { + return cli.Exit(err.Error(), 1) + } return nil }, } - return &command } func Test(folder string, runFrontend bool, runBackend bool) error { var wg sync.WaitGroup + errChan := make(chan error, 1) + var errOccurred bool // Flag to indicate whether an error has occurred // Start the backend if specified - if runBackend { + if runBackend && !errOccurred { wg.Add(1) go func() { defer wg.Done() - BackendTest() + err := BackendTest() + if err != nil { + errChan <- err + errOccurred = true + } }() } // Start the frontend if specified - if runFrontend { + if runFrontend && !errOccurred { wg.Add(1) go func() { defer wg.Done() - FrontendTest(folder) + err := FrontendTest(folder) + if err != nil { + errChan <- err + errOccurred = true + } }() } - wg.Wait() + go func() { + wg.Wait() + close(errChan) + }() + + for err := range errChan { + if err != nil { + return err + } + } return nil } func BackendTest() error { - // rootDir, err := utils.GetRootDir() - // if err != nil { - // return cli.Exit("Couldn't find the project root", 1) - // } - cmd := exec.Command("go", "test", "./...") - cmd.Dir = BACKEND_DIR + "/.." + cmd.Dir = fmt.Sprintf("%s/..", BACKEND_DIR) out, err := cmd.CombinedOutput() if err != nil { @@ -90,12 +105,9 @@ func BackendTest() error { fmt.Println(string(out)) - cmd = exec.Command("./scripts/clean_old_test_dbs.sh") - cmd.Dir = ROOT_DIR - - err = cmd.Run() + err = CleanTestDBs() if err != nil { - return cli.Exit("Failed to clean old test databases", 1) + return cli.Exit(err.Error(), 1) } return nil diff --git a/cli/go.mod b/cli/go.mod index 3c4a30d00..ef0743b53 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -1,11 +1,39 @@ module github.com/GenerateNU/sac/cli -go 1.21.1 +go 1.22.0 -require github.com/urfave/cli/v2 v2.27.1 +require ( + github.com/GenerateNU/sac/backend v0.0.0-20240208151800-c7c93bbd1bb7 + github.com/lib/pq v1.10.9 + github.com/urfave/cli/v2 v2.27.1 +) require ( + github.com/awnumar/memcall v0.2.0 // indirect + github.com/awnumar/memguard v0.22.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/garrettladley/mattress v0.2.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.18.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cli/go.sum b/cli/go.sum index 31517c04a..df51b4c76 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,8 +1,90 @@ +github.com/GenerateNU/sac/backend v0.0.0-20240208151800-c7c93bbd1bb7 h1:PiMBcw9KZxoH1pSfyyQsiisyemef2TwCG/WSSYailYw= +github.com/GenerateNU/sac/backend v0.0.0-20240208151800-c7c93bbd1bb7/go.mod h1:x3FsVBrEq6k60rGtxaOLg3UJ35BIOh6pyjRdGdte5bo= +github.com/awnumar/memcall v0.2.0 h1:sRaogqExTOOkkNwO9pzJsL8jrOV29UuUW7teRMfbqtI= +github.com/awnumar/memcall v0.2.0/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo= +github.com/awnumar/memguard v0.22.4 h1:1PLgKcgGPeExPHL8dCOWGVjIbQUBgJv9OL0F/yE1PqQ= +github.com/awnumar/memguard v0.22.4/go.mod h1:+APmZGThMBWjnMlKiSM1X7MVpbIVewen2MTkqWkA/zE= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/garrettladley/mattress v0.2.0 h1:+XUdsv9NO2s4JL+8exvAFziw0b1kv/0WlQo2Dlxat+w= +github.com/garrettladley/mattress v0.2.0/go.mod h1:OWKIRc9wC3gtD3Ng/nUuNEiR1TJvRYLmn/KZYw9nl5Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cli/main b/cli/main deleted file mode 100755 index ac935ebad..000000000 Binary files a/cli/main and /dev/null differ diff --git a/cli/main.go b/cli/main.go index df7221ad8..6967add90 100755 --- a/cli/main.go +++ b/cli/main.go @@ -16,14 +16,15 @@ func main() { commands.SwaggerCommand(), commands.ClearDBCommand(), commands.MigrateCommand(), - commands.ResetDBCommand(), - commands.DropDBCommand(), - commands.TestCommand(), // TODO: frontend tests + commands.ResetCommand(), + commands.InsertCommand(), + commands.DropCommand(), + commands.RunBackendCommand(), + commands.TestCommand(), // TODO: frontend tests commands.FormatCommand(), // TODO: frontend format - commands.LintCommand(), // TODO: frontend lint + commands.LintCommand(), // TODO: frontend lint }, } - err := app.Run(os.Args) if err != nil { log.Fatal(err) diff --git a/cli/utils/path.go b/cli/utils/path.go index d91a6b504..01b9a6a1a 100644 --- a/cli/utils/path.go +++ b/cli/utils/path.go @@ -6,7 +6,6 @@ import ( "path/filepath" ) - func GetRootDir() (string, error) { // Get the current working directory currentDir, err := os.Getwd() @@ -41,4 +40,4 @@ func FindRootDir(dir string) (string, error) { // Recursively search in the parent directory return FindRootDir(parentDir) -} \ No newline at end of file +} diff --git a/config/local.yml b/config/local.yml index d500bb335..2ad38c2dd 100644 --- a/config/local.yml +++ b/config/local.yml @@ -11,3 +11,8 @@ database: requiressl: false superuser: password: password +auth: + accesskey: g(r|##*?>\Qp}h37e+,T2 + accesstokenexpiry: 60 # in minutes + refreshkey: amk*2!gG}1i"8D9RwJS$p + refreshtokenexpiry: 30 # in days diff --git a/frontend/node_modules/.yarn-integrity b/frontend/node_modules/.yarn-integrity deleted file mode 100644 index 044a5ddd9..000000000 --- a/frontend/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "darwin-arm64-115", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/frontend/sac-mobile/package.json b/frontend/sac-mobile/package.json index 54a0e1b62..4ec914d5b 100644 --- a/frontend/sac-mobile/package.json +++ b/frontend/sac-mobile/package.json @@ -21,40 +21,41 @@ "@tanstack/react-query": "^5.17.9", "clsx": "^2.1.0", "eslint-config-prettier": "^9.1.0", - "expo": "~49.0.18", - "expo-auth-session": "~5.0.2", - "expo-font": "~11.4.0", - "expo-linking": "~5.0.2", - "expo-router": "^2.0.0", - "expo-splash-screen": "~0.20.5", - "expo-status-bar": "~1.6.0", - "expo-system-ui": "~2.4.0", - "expo-web-browser": "~12.3.2", + "expo": "~50.0.4", + "expo-auth-session": "~5.4.0", + "expo-font": "~11.10.2", + "expo-linking": "~6.2.2", + "expo-router": "^3.4.6", + "expo-splash-screen": "~0.26.4", + "expo-status-bar": "~1.11.1", + "expo-system-ui": "~2.9.3", + "expo-web-browser": "~12.8.2", "nativewind": "^2.0.11", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.72.6", - "react-native-gesture-handler": "~2.12.0", - "react-native-safe-area-context": "4.6.3", - "react-native-screens": "~3.22.0", + "react-native": "0.73.3", + "react-native-gesture-handler": "~2.14.1", + "react-native-safe-area-context": "4.8.2", + "react-native-screens": "~3.29.0", "react-native-web": "~0.19.6", + "semver": "7.6.0", "zod": "^3.22.4", - "zustand": "^4.4.7" + "zustand": "^4.5.0" }, "devDependencies": { - "@babel/core": "^7.20.0", + "@babel/core": "^7.23.9", "@react-native-community/eslint-config": "^3.2.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/react": "~18.2.14", - "autoprefixer": "^10.4.16", + "autoprefixer": "^10.4.17", "eslint": "^8.56.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.2.1", - "jest-expo": "~49.0.0", + "jest-expo": "~50.0.1", "postcss": "^8.4.33", - "prettier": "^3.2.1", + "prettier": "^3.2.4", "react-test-renderer": "18.2.0", - "tailwindcss": "3.3.2", + "tailwindcss": "3.4.1", "typescript": "^5.1.3" }, "overrides": { diff --git a/frontend/sac-mobile/yarn.lock b/frontend/sac-mobile/yarn.lock index 4c9b3cbcb..ef959b6a9 100644 --- a/frontend/sac-mobile/yarn.lock +++ b/frontend/sac-mobile/yarn.lock @@ -45,21 +45,21 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.0": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" - integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.0", "@babel/core@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz#b028820718000f267870822fec434820e9b1e4d1" + integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.23.5" "@babel/generator" "^7.23.6" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.7" - "@babel/parser" "^7.23.6" - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/helpers" "^7.23.9" + "@babel/parser" "^7.23.9" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -84,7 +84,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.18.7", "@babel/generator@^7.20.0", "@babel/generator@^7.23.0", "@babel/generator@^7.23.6", "@babel/generator@^7.7.2": +"@babel/generator@^7.18.7", "@babel/generator@^7.20.0", "@babel/generator@^7.20.5", "@babel/generator@^7.23.0", "@babel/generator@^7.23.6", "@babel/generator@^7.7.2": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== @@ -213,7 +213,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== @@ -281,14 +281,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.23.7": - version "7.23.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" - integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== +"@babel/helpers@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d" + integrity sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ== dependencies: - "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.7" - "@babel/types" "^7.23.6" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" "@babel/highlight@^7.10.4", "@babel/highlight@^7.23.4": version "7.23.4" @@ -299,10 +299,10 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0", "@babel/parser@^7.23.6": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" - integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.0", "@babel/parser@^7.20.5", "@babel/parser@^7.20.7", "@babel/parser@^7.23.0", "@babel/parser@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" @@ -363,14 +363,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-default-from" "^7.23.3" -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" @@ -387,7 +379,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.12.13", "@babel/plugin-proposal-object-rest-spread@^7.20.0": +"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.20.0": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== @@ -706,7 +698,7 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.23.4": +"@babel/plugin-transform-export-namespace-from@^7.22.11", "@babel/plugin-transform-export-namespace-from@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" integrity sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ== @@ -835,7 +827,7 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.23.4": +"@babel/plugin-transform-object-rest-spread@^7.12.13", "@babel/plugin-transform-object-rest-spread@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz#2b9c2d26bf62710460bdc0d1730d4f1048361b83" integrity sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g== @@ -871,14 +863,14 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.23.3": +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.22.15", "@babel/plugin-transform-parameters@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" integrity sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-methods@^7.23.3": +"@babel/plugin-transform-private-methods@^7.22.5", "@babel/plugin-transform-private-methods@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" integrity sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g== @@ -886,7 +878,7 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.23.4": +"@babel/plugin-transform-private-property-in-object@^7.22.11", "@babel/plugin-transform-private-property-in-object@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" integrity sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A== @@ -903,13 +895,20 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-display-name@^7.0.0": +"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" integrity sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.22.5" + "@babel/plugin-transform-react-jsx-self@^7.0.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz#ed3e7dadde046cce761a8e3cf003a13d1a7972d9" @@ -924,7 +923,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.12.17": +"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312" integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA== @@ -935,6 +934,14 @@ "@babel/plugin-syntax-jsx" "^7.23.3" "@babel/types" "^7.23.4" +"@babel/plugin-transform-react-pure-annotations@^7.23.3": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz#fabedbdb8ee40edf5da96f3ecfc6958e3783b93c" + integrity sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-transform-regenerator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c" @@ -1143,6 +1150,18 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-react@^7.22.15": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.23.3.tgz#f73ca07e7590f977db07eb54dbe46538cc015709" + integrity sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-react-display-name" "^7.23.3" + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.23.3" + "@babel/preset-typescript@^7.13.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz#14534b34ed5b6d435aa05f1ae1c5e7adcc01d913" @@ -1177,14 +1196,14 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.0.0", "@babel/template@^7.22.15", "@babel/template@^7.3.3": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== +"@babel/template@^7.0.0", "@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" + integrity sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA== dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" "@babel/traverse@7.23.2": version "7.23.2" @@ -1202,10 +1221,10 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.20.0", "@babel/traverse@^7.23.7": - version "7.23.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" - integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== +"@babel/traverse@^7.20.0", "@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== dependencies: "@babel/code-frame" "^7.23.5" "@babel/generator" "^7.23.6" @@ -1213,8 +1232,8 @@ "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.6" - "@babel/types" "^7.23.6" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" debug "^4.3.1" globals "^11.1.0" @@ -1235,20 +1254,15 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.23.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" - integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== +"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== dependencies: "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@bacons/react-views@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@bacons/react-views/-/react-views-1.1.3.tgz#06e7ae8803cc363d13d6fd06b828e10ad8bb3910" - integrity sha512-aLipQAkQKRzG64e28XHBpByyBPfANz0A6POqYHGyryHizG9vLCLNQwLe8gwFANEMBWW2Mx5YdQ7RkNdQMQ+CXQ== - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1445,7 +1459,7 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": +"@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b" integrity sha512-Ydf4LidRB/EBI+YrB+cVLqIseiRfjUI/AeHBgjGMtq3GroraDu81OV7zqophRgupngoL3iS3JUMDMnxO7g39qA== @@ -1455,67 +1469,80 @@ mv "~2" safe-json-stringify "~1" -"@expo/cli@0.10.16": - version "0.10.16" - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.10.16.tgz#42f9aaf08884f70f3a671b7d6b4f138ad39192d7" - integrity sha512-EwgnRN5AMElg0JJjFLJTPk5hYkVXxnNMLIvZBiTfGoCq+rDw6u7Mg5l2Bbm/geSHOoplaHyPZ/Wr23FAuZWehA== +"@expo/cli@0.17.3": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.17.3.tgz#0763696671522062e5592411d02952ed905d4838" + integrity sha512-lIK8igsEQxTh4WuDlcEhE0wAJcDrAyjWDF00phdmwuSCpE5SaEXNlddOXvGxEVKPhUxHZUFo9NbfoQC+JVmkfA== dependencies: "@babel/runtime" "^7.20.0" "@expo/code-signing-certificates" "0.0.5" - "@expo/config" "~8.1.0" - "@expo/config-plugins" "~7.2.0" - "@expo/dev-server" "0.5.5" + "@expo/config" "~8.5.0" + "@expo/config-plugins" "~7.8.0" "@expo/devcert" "^1.0.0" - "@expo/env" "0.0.5" + "@expo/env" "~0.2.0" + "@expo/image-utils" "^0.4.0" "@expo/json-file" "^8.2.37" - "@expo/metro-config" "~0.10.0" + "@expo/metro-config" "~0.17.0" "@expo/osascript" "^2.0.31" - "@expo/package-manager" "~1.1.0" - "@expo/plist" "^0.0.20" - "@expo/prebuild-config" "6.2.6" + "@expo/package-manager" "^1.1.1" + "@expo/plist" "^0.1.0" + "@expo/prebuild-config" "6.7.4" "@expo/rudder-sdk-node" "1.1.1" "@expo/spawn-async" "1.5.0" - "@expo/xcpretty" "^4.2.1" + "@expo/xcpretty" "^4.3.0" + "@react-native/dev-middleware" "^0.73.6" "@urql/core" "2.3.6" "@urql/exchange-retry" "0.3.0" accepts "^1.3.8" - arg "4.1.0" + arg "5.0.2" better-opn "~3.0.2" bplist-parser "^0.3.1" cacache "^15.3.0" chalk "^4.0.0" ci-info "^3.3.0" + connect "^3.7.0" debug "^4.3.4" env-editor "^0.4.1" + find-yarn-workspace-root "~2.0.0" form-data "^3.0.1" freeport-async "2.0.0" fs-extra "~8.1.0" getenv "^1.0.0" + glob "^7.1.7" graphql "15.8.0" graphql-tag "^2.10.1" https-proxy-agent "^5.0.1" internal-ip "4.3.0" + is-docker "^2.0.0" + is-wsl "^2.1.1" js-yaml "^3.13.1" json-schema-deref-sync "^0.13.0" - md5-file "^3.2.3" + lodash.debounce "^4.0.8" md5hex "^1.0.0" - minipass "3.1.6" + minimatch "^3.0.4" + minipass "3.3.6" node-fetch "^2.6.7" node-forge "^1.3.1" npm-package-arg "^7.0.0" + open "^8.3.0" ora "3.4.0" + picomatch "^3.0.1" pretty-bytes "5.6.0" progress "2.0.3" prompts "^2.3.2" qrcode-terminal "0.11.0" require-from-string "^2.0.2" requireg "^0.2.2" + resolve "^1.22.2" resolve-from "^5.0.0" + resolve.exports "^2.0.2" semver "^7.5.3" send "^0.18.0" slugify "^1.3.4" + source-map-support "~0.5.21" structured-headers "^0.4.1" tar "^6.0.5" + temp-dir "^2.0.0" tempy "^0.7.1" terminal-link "^2.1.1" text-table "^0.2.0" @@ -1531,7 +1558,30 @@ node-forge "^1.2.1" nullthrows "^1.1.1" -"@expo/config-plugins@7.2.5", "@expo/config-plugins@~7.2.0": +"@expo/config-plugins@7.8.4", "@expo/config-plugins@~7.8.0", "@expo/config-plugins@~7.8.2": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.4.tgz#533b5d536c1dc8b5544d64878b51bda28f2e1a1f" + integrity sha512-hv03HYxb/5kX8Gxv/BTI8TLc9L06WzqAfHRRXdbar4zkLcP2oTzvsLEF4/L/TIpD3rsnYa0KU42d0gWRxzPCJg== + dependencies: + "@expo/config-types" "^50.0.0-alpha.1" + "@expo/fingerprint" "^0.6.0" + "@expo/json-file" "~8.3.0" + "@expo/plist" "^0.1.0" + "@expo/sdk-runtime-versions" "^1.0.0" + "@react-native/normalize-color" "^2.0.0" + chalk "^4.1.2" + debug "^4.3.1" + find-up "~5.0.0" + getenv "^1.0.0" + glob "7.1.6" + resolve-from "^5.0.0" + semver "^7.5.3" + slash "^3.0.0" + slugify "^1.6.6" + xcode "^3.0.1" + xml2js "0.6.0" + +"@expo/config-plugins@~7.2.0": version "7.2.5" resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.2.5.tgz#b15f22878975fdc4ddcfa8cdc971937ddc4c0249" integrity sha512-w+5ccu1IxBHgyQk9CPFKLZOk8yZQEyTjbJwOzESK1eR7QwosbcsLkN1c1WWUZYiCXwORu3UTwJYll4+X2xxJhQ== @@ -1557,7 +1607,29 @@ resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-49.0.0.tgz#15ffef715285c06703f6fb7ec0cda853f645cc09" integrity sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA== -"@expo/config@8.1.2", "@expo/config@~8.1.0": +"@expo/config-types@^50.0.0", "@expo/config-types@^50.0.0-alpha.1": + version "50.0.0" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-50.0.0.tgz#b534d3ec997ec60f8af24f6ad56244c8afc71a0b" + integrity sha512-0kkhIwXRT6EdFDwn+zTg9R2MZIAEYGn1MVkyRohAd+C9cXOb5RA8WLQi7vuxKF9m1SMtNAUrf0pO+ENK0+/KSw== + +"@expo/config@8.5.4", "@expo/config@~8.5.0": + version "8.5.4" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.5.4.tgz#bb5eb06caa36e4e35dc8c7647fae63e147b830ca" + integrity sha512-ggOLJPHGzJSJHVBC1LzwXwR6qUn8Mw7hkc5zEKRIdhFRuIQ6s2FE4eOvP87LrNfDF7eZGa6tJQYsiHSmZKG+8Q== + dependencies: + "@babel/code-frame" "~7.10.4" + "@expo/config-plugins" "~7.8.2" + "@expo/config-types" "^50.0.0" + "@expo/json-file" "^8.2.37" + getenv "^1.0.0" + glob "7.1.6" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + semver "7.5.3" + slugify "^1.3.4" + sucrase "3.34.0" + +"@expo/config@~8.1.0": version "8.1.2" resolved "https://registry.yarnpkg.com/@expo/config/-/config-8.1.2.tgz#7fff28b3acefe39702e9f3ce1c9fd896a52caa80" integrity sha512-4e7hzPj50mQIlsrzOH6XZ36O094mPfPTIDIH4yv49bWNMc7GFLTofB/lcT+QyxiLaJuC0Wlk9yOLB8DIqmtwug== @@ -1574,27 +1646,6 @@ slugify "^1.3.4" sucrase "^3.20.0" -"@expo/dev-server@0.5.5": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@expo/dev-server/-/dev-server-0.5.5.tgz#33f9065e0cf5f36ac61944a92d11390cc71b7035" - integrity sha512-t0fT8xH1exwYsH5hh7bAt85VF+gXxg24qrbny2rR/iKoPTWFCd2JNQV8pvfLg51hvrywQ3YCBuT3lU1w7aZxFA== - dependencies: - "@expo/bunyan" "4.0.0" - "@expo/metro-config" "~0.10.0" - "@expo/osascript" "2.0.33" - "@expo/spawn-async" "^1.5.0" - body-parser "^1.20.1" - chalk "^4.0.0" - connect "^3.7.0" - fs-extra "9.0.0" - is-docker "^2.0.0" - is-wsl "^2.1.1" - node-fetch "^2.6.0" - open "^8.3.0" - resolve-from "^5.0.0" - serialize-error "6.0.0" - temp-dir "^2.0.0" - "@expo/devcert@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.1.0.tgz#d148eb9180db6753c438192e73a123fb13b662ac" @@ -1614,10 +1665,10 @@ tmp "^0.0.33" tslib "^2.4.0" -"@expo/env@0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.0.5.tgz#86526ed5c966fc39b2644341f7a10f4b855e59b8" - integrity sha512-UXuKAqyXfhMQC3gP0OyjXmFX08Z1fkVWiGBN7bYzfoX8LHatjeHrDtI6w5nDvd8XPxPvmqaZoEDw1lW3+dz3oQ== +"@expo/env@~0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.2.1.tgz#51b5e836ce510cbb18341ad2ab2f685354fd4a94" + integrity sha512-deZmRS7Dvp18VM8s559dq/ZjPlV1D9vtLoLXwHmCK/JYOvtNptdKsfxcWjI7ewmo6ln2PqgNI9HRI74q6Wk2eA== dependencies: chalk "^4.0.0" debug "^4.3.4" @@ -1625,6 +1676,19 @@ dotenv-expand "~10.0.0" getenv "^1.0.0" +"@expo/fingerprint@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.6.0.tgz#77366934673d4ecea37284109b4dd67f9e6a7487" + integrity sha512-KfpoVRTMwMNJ/Cf5o+Ou8M/Y0EGSTqK+rbi70M2Y0K2qgWNfMJ1gm6sYO9uc8lcTr7YSYM1Rme3dk7QXhpScNA== + dependencies: + "@expo/spawn-async" "^1.5.0" + chalk "^4.1.2" + debug "^4.3.4" + find-up "^5.0.0" + minimatch "^3.0.4" + p-limit "^3.1.0" + resolve-from "^5.0.0" + "@expo/image-utils@0.3.22": version "0.3.22" resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.3.22.tgz#3a45fb2e268d20fcc761c87bca3aca7fd8e24260" @@ -1642,7 +1706,32 @@ semver "7.3.2" tempy "0.3.0" -"@expo/json-file@^8.2.37", "@expo/json-file@~8.2.37": +"@expo/image-utils@^0.4.0": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.4.1.tgz#78c54b8aaa974d0ac37fee5285683b54ff161b2c" + integrity sha512-EZb+VHSmw+a5s2hS9qksTcWylY0FDaIAVufcxoaRS9tHIXLjW5zcKW7Rhj9dSEbZbRVy9yXXdHKa3GQdUQIOFw== + dependencies: + "@expo/spawn-async" "1.5.0" + chalk "^4.0.0" + fs-extra "9.0.0" + getenv "^1.0.0" + jimp-compact "0.16.1" + node-fetch "^2.6.0" + parse-png "^2.1.0" + resolve-from "^5.0.0" + semver "7.3.2" + tempy "0.3.0" + +"@expo/json-file@^8.2.37", "@expo/json-file@~8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.3.0.tgz#fc84af77b532a4e9bfb5beafd0e3b7f692b6bd7e" + integrity sha512-yROUeXJXR5goagB8c3muFLCzLmdGOvoPpR5yDNaXrnTp4euNykr9yW0wWhJx4YVRTNOPtGBnEbbJBW+a9q+S6g== + dependencies: + "@babel/code-frame" "~7.10.4" + json5 "^2.2.2" + write-file-atomic "^2.3.0" + +"@expo/json-file@~8.2.37": version "8.2.37" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.2.37.tgz#9c02d3b42134907c69cc0a027b18671b69344049" integrity sha512-YaH6rVg11JoTS2P6LsW7ybS2CULjf40AbnAHw2F1eDPuheprNjARZMnyHFPkKv7GuxCy+B9GPcbOKgc4cgA80Q== @@ -1651,33 +1740,38 @@ json5 "^2.2.2" write-file-atomic "^2.3.0" -"@expo/metro-config@~0.10.0": - version "0.10.7" - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.10.7.tgz#d1b91baffcb7feb52fc7e2e122450bfc5d01e7c1" - integrity sha512-uACymEiyX0447hI4unt+2cemLQkTZXKvTev936NhtsgVnql45EP0V0pzmo/0H0WlHaAGXgvOBZJl8wFqcJ3CbQ== +"@expo/metro-config@0.17.3", "@expo/metro-config@~0.17.0": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.17.3.tgz#f06f0929e4ac907517d24794d35021901651da49" + integrity sha512-YW8ixbaz6yL7/Mg1rJJejiAAVQQKjGY1wXvT2Dh487r/r9/j1yE1YRS/oRY1yItYzbnHvO0p0jMnEGfiFYL3Tg== dependencies: - "@expo/config" "~8.1.0" - "@expo/env" "0.0.5" - "@expo/json-file" "~8.2.37" + "@babel/core" "^7.20.0" + "@babel/generator" "^7.20.5" + "@babel/parser" "^7.20.0" + "@babel/types" "^7.20.0" + "@expo/config" "~8.5.0" + "@expo/env" "~0.2.0" + "@expo/json-file" "~8.3.0" + "@expo/spawn-async" "^1.7.2" + babel-preset-fbjs "^3.4.0" chalk "^4.1.0" debug "^4.3.2" find-yarn-workspace-root "~2.0.0" + fs-extra "^9.1.0" getenv "^1.0.0" + glob "^7.2.3" jsc-safe-url "^0.2.4" lightningcss "~1.19.0" - postcss "~8.4.21" + postcss "~8.4.32" resolve-from "^5.0.0" - sucrase "^3.20.0" + sucrase "3.34.0" -"@expo/metro-runtime@2.2.16": - version "2.2.16" - resolved "https://registry.yarnpkg.com/@expo/metro-runtime/-/metro-runtime-2.2.16.tgz#dad958d74ad4a432a1f729d7a7313ec44083c656" - integrity sha512-WOUe7ByZsQpFRifyh9WgsjMYrCGHirWA8VvtR5fs+vi0za3yFIaC89wYMvEZILyvn+RIe7Ysln8nzF4xgtnKFg== - dependencies: - "@bacons/react-views" "^1.1.3" - qs "^6.10.3" +"@expo/metro-runtime@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@expo/metro-runtime/-/metro-runtime-3.1.2.tgz#4cc4fe53aa8f716307f584c67db76268d38c2ed7" + integrity sha512-Wekv2AZ3HY0NG9Im8AMB1KHGyHjmALg6xekVK34724I/DLtcocLKtQEP04oI9GcAZBotAhROHH5E4ADhJAEgYg== -"@expo/osascript@2.0.33", "@expo/osascript@^2.0.31": +"@expo/osascript@^2.0.31": version "2.0.33" resolved "https://registry.yarnpkg.com/@expo/osascript/-/osascript-2.0.33.tgz#e9dcc8da54466c11939074aa71a006024ea884b1" integrity sha512-FQinlwHrTlJbntp8a7NAlCKedVXe06Va/0DSLXRO8lZVtgbEMrYYSUZWQNcOlNtc58c2elNph6z9dMOYwSo3JQ== @@ -1685,10 +1779,10 @@ "@expo/spawn-async" "^1.5.0" exec-async "^2.2.0" -"@expo/package-manager@~1.1.0": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.1.2.tgz#e58c9bed4cbb829ebf2cbb80b8542600a6609bd1" - integrity sha512-JI9XzrxB0QVXysyuJ996FPCJGDCYRkbUvgG4QmMTTMFA1T+mv8YzazC3T9C1pHQUAAveVCre1+Pqv0nZXN24Xg== +"@expo/package-manager@^1.1.1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@expo/package-manager/-/package-manager-1.4.2.tgz#8c12a9163c5ff7c7cc89806c4b75cff4974c57fc" + integrity sha512-LKdo/6y4W7llZ6ghsg1kdx2CeH/qR/c6QI/JI8oPUvppsZoeIYjSkdflce978fAMfR8IXoi0wt0jA2w0kWpwbg== dependencies: "@expo/json-file" "^8.2.37" "@expo/spawn-async" "^1.5.0" @@ -1699,6 +1793,7 @@ js-yaml "^3.13.1" micromatch "^4.0.2" npm-package-arg "^7.0.0" + ora "^3.4.0" split "^1.0.1" sudo-prompt "9.1.1" @@ -1711,6 +1806,15 @@ base64-js "^1.2.3" xmlbuilder "^14.0.0" +"@expo/plist@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.1.0.tgz#eabc95f951d14e10c87fd0443ee01d567371f058" + integrity sha512-xWD+8vIFif0wKyuqe3fmnmnSouXYucciZXFzS0ZD5OV9eSAS1RGQI5FaGGJ6zxJ4mpdy/4QzbLdBjnYE5vxA0g== + dependencies: + "@xmldom/xmldom" "~0.7.7" + base64-js "^1.2.3" + xmlbuilder "^14.0.0" + "@expo/prebuild-config@6.2.6": version "6.2.6" resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-6.2.6.tgz#c5b4f8adcba4be00c874d6b24a8267d45c555261" @@ -1727,6 +1831,22 @@ semver "7.5.3" xml2js "0.6.0" +"@expo/prebuild-config@6.7.4": + version "6.7.4" + resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-6.7.4.tgz#b3e4c8545d7a101bf1fc263c5b7290abc4635e69" + integrity sha512-x8EUdCa8DTMZ/dtEXjHAdlP+ljf6oSeSKNzhycXiHhpMSMG9jEhV28ocCwc6cKsjK5GziweEiHwvrj6+vsBlhA== + dependencies: + "@expo/config" "~8.5.0" + "@expo/config-plugins" "~7.8.0" + "@expo/config-types" "^50.0.0-alpha.1" + "@expo/image-utils" "^0.4.0" + "@expo/json-file" "^8.2.37" + debug "^4.3.1" + fs-extra "^9.0.0" + resolve-from "^5.0.0" + semver "7.5.3" + xml2js "0.6.0" + "@expo/rudder-sdk-node@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz#6aa575f346833eb6290282118766d4919c808c6a" @@ -1745,6 +1865,16 @@ resolved "https://registry.yarnpkg.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c" integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ== +"@expo/server@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@expo/server/-/server-0.3.0.tgz#b16767999382b0f5ea88d86609a5ceabcff1388c" + integrity sha512-5oIqedpLVMnf1LGI9Xd5OOGmK3DjgH9VpuqVN4e/6DwLT05RZJMyI7ylfG6QSy1e44yOgjv242tLyg0e/zdZ+A== + dependencies: + "@remix-run/node" "^1.19.3" + abort-controller "^3.0.0" + debug "^4.3.4" + source-map-support "~0.5.21" + "@expo/spawn-async@1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.5.0.tgz#799827edd8c10ef07eb1a2ff9dcfe081d596a395" @@ -1752,7 +1882,7 @@ dependencies: cross-spawn "^6.0.5" -"@expo/spawn-async@^1.5.0": +"@expo/spawn-async@^1.5.0", "@expo/spawn-async@^1.7.2": version "1.7.2" resolved "https://registry.yarnpkg.com/@expo/spawn-async/-/spawn-async-1.7.2.tgz#fcfe66c3e387245e72154b1a7eae8cada6a47f58" integrity sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew== @@ -1764,10 +1894,15 @@ resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-13.0.0.tgz#e2989b85e95a82bce216f88cf8fb583ab050ec95" integrity sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA== -"@expo/xcpretty@^4.2.1": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.3.0.tgz#d745c2c5ec38fc6acd451112bb05c6ae952a2c3a" - integrity sha512-whBbvHZ2Q10T5TNmN0z5NbO6C9ZDw+XUTu8h6vVMnMzQrbGexc9oaCCZfz+L3Q7TEL5vfr+9L86nY62c3Bsm+g== +"@expo/vector-icons@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-14.0.0.tgz#48ce0aa5c05873b07c0c78bfe16c870388f4de9a" + integrity sha512-5orm59pdnBQlovhU9k4DbjMUZBHNlku7IRgFY56f7pcaaCnXq9yaLJoOQl9sMwNdFzf4gnkTyHmR5uN10mI9rA== + +"@expo/xcpretty@^4.3.0": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@expo/xcpretty/-/xcpretty-4.3.1.tgz#e0a6a92d1e46ab5ac5e90d9a8e66ac1a2a2f5920" + integrity sha512-sqXgo1SCv+j4VtYEwl/bukuOIBrVgx6euIoCat3Iyx5oeoXwEA2USCoeL0IPubflMxncA2INkqJ/Wr3NGrSgzw== dependencies: "@babel/code-frame" "7.10.4" chalk "^4.1.0" @@ -1868,6 +2003,11 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/ttlcache@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" + integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1930,7 +2070,7 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/create-cache-key-function@^29.2.1": +"@jest/create-cache-key-function@^29.2.1", "@jest/create-cache-key-function@^29.6.3": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== @@ -2082,17 +2222,6 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" @@ -2214,117 +2343,104 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.0" -"@react-native-community/cli-clean@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-11.3.7.tgz#cb4c2f225f78593412c2d191b55b8570f409a48f" - integrity sha512-twtsv54ohcRyWVzPXL3F9VHGb4Qhn3slqqRs3wEuRzjR7cTmV2TIO2b1VhaqF4HlCgNd+cGuirvLtK2JJyaxMg== +"@react-native-community/cli-clean@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz#d4f1730c3d22d816b4d513d330d5f3896a3f5921" + integrity sha512-90k2hCX0ddSFPT7EN7h5SZj0XZPXP0+y/++v262hssoey3nhurwF57NGWN0XAR0o9BSW7+mBfeInfabzDraO6A== dependencies: - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" execa "^5.0.0" - prompts "^2.4.0" -"@react-native-community/cli-config@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-11.3.7.tgz#4ce95548252ecb094b576369abebf9867c95d277" - integrity sha512-FDBLku9xskS+bx0YFJFLCmUJhEZ4/MMSC9qPYOGBollWYdgE7k/TWI0IeYFmMALAnbCdKQAYP5N29N55Tad8lg== +"@react-native-community/cli-config@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-12.3.2.tgz#1a5de302de4d597ff2fc9932a032134b6ec4325f" + integrity sha512-UUCzDjQgvAVL/57rL7eOuFUhd+d+6qfM7V8uOegQFeFEmSmvUUDLYoXpBa5vAK9JgQtSqMBJ1Shmwao+/oElxQ== dependencies: - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" cosmiconfig "^5.1.0" deepmerge "^4.3.0" glob "^7.1.3" joi "^17.2.1" -"@react-native-community/cli-debugger-ui@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.7.tgz#2147b73313af8de3c9b396406d5d344b904cf2bb" - integrity sha512-aVmKuPKHZENR8SrflkMurZqeyLwbKieHdOvaZCh1Nn/0UC5CxWcyST2DB2XQboZwsvr3/WXKJkSUO+SZ1J9qTQ== +"@react-native-community/cli-debugger-ui@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.2.tgz#b2743876b03e560fbf5ef516e95387fcb6d91630" + integrity sha512-nSWQUL+51J682DlfcC1bjkUbQbGvHCC25jpqTwHIjmmVjYCX1uHuhPSqQKgPNdvtfOkrkACxczd7kVMmetxY2Q== dependencies: serve-static "^1.13.1" -"@react-native-community/cli-doctor@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-11.3.7.tgz#7d5f5b1aea78134bba713fa97795986345ff1344" - integrity sha512-YEHUqWISOHnsl5+NM14KHelKh68Sr5/HeEZvvNdIcvcKtZic3FU7Xd1WcbNdo3gCq5JvzGFfufx02Tabh5zmrg== +"@react-native-community/cli-doctor@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-12.3.2.tgz#9e82b49f04ee03872b2975f26c8799cecac021ce" + integrity sha512-GrAabdY4qtBX49knHFvEAdLtCjkmndjTeqhYO6BhsbAeKOtspcLT/0WRgdLIaKODRa61ADNB3K5Zm4dU0QrZOg== dependencies: - "@react-native-community/cli-config" "11.3.7" - "@react-native-community/cli-platform-android" "11.3.7" - "@react-native-community/cli-platform-ios" "11.3.7" - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-config" "12.3.2" + "@react-native-community/cli-platform-android" "12.3.2" + "@react-native-community/cli-platform-ios" "12.3.2" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" command-exists "^1.2.8" - envinfo "^7.7.2" + deepmerge "^4.3.0" + envinfo "^7.10.0" execa "^5.0.0" hermes-profile-transformer "^0.0.6" ip "^1.1.5" node-stream-zip "^1.9.1" ora "^5.4.1" - prompts "^2.4.0" semver "^7.5.2" strip-ansi "^5.2.0" - sudo-prompt "^9.0.0" wcwidth "^1.0.1" yaml "^2.2.1" -"@react-native-community/cli-hermes@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-11.3.7.tgz#091e730a1f8bace6c3729e8744bad6141002e0e8" - integrity sha512-chkKd8n/xeZkinRvtH6QcYA8rjNOKU3S3Lw/3Psxgx+hAYV0Gyk95qJHTalx7iu+PwjOOqqvCkJo5jCkYLkoqw== +"@react-native-community/cli-hermes@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-12.3.2.tgz#5f266985fe32a37e9020e881460e9017870be2e5" + integrity sha512-SL6F9O8ghp4ESBFH2YAPLtIN39jdnvGBKnK4FGKpDCjtB3DnUmDsGFlH46S+GGt5M6VzfG2eeKEOKf3pZ6jUzA== dependencies: - "@react-native-community/cli-platform-android" "11.3.7" - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-platform-android" "12.3.2" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" hermes-profile-transformer "^0.0.6" ip "^1.1.5" -"@react-native-community/cli-platform-android@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.7.tgz#7845bc48258b6bb55df208a23b3690647f113995" - integrity sha512-WGtXI/Rm178UQb8bu1TAeFC/RJvYGnbHpULXvE20GkmeJ1HIrMjkagyk6kkY3Ej25JAP2R878gv+TJ/XiRhaEg== +"@react-native-community/cli-platform-android@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.2.tgz#de54d89712f8ea95046d798ec274fd6caea70c34" + integrity sha512-MZ5nO8yi/N+Fj2i9BJcJ9C/ez+9/Ir7lQt49DWRo9YDmzye66mYLr/P2l/qxsixllbbDi7BXrlLpxaEhMrDopg== dependencies: - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" execa "^5.0.0" + fast-xml-parser "^4.2.4" glob "^7.1.3" logkitty "^0.7.1" -"@react-native-community/cli-platform-ios@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.7.tgz#87478f907634713b7236c77870446a5ca1f35ff1" - integrity sha512-Z/8rseBput49EldX7MogvN6zJlWzZ/4M97s2P+zjS09ZoBU7I0eOKLi0N9wx+95FNBvGQQ/0P62bB9UaFQH2jw== +"@react-native-community/cli-platform-ios@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.2.tgz#07e298f69761424da85909790a43ec60ebfe6097" + integrity sha512-OcWEAbkev1IL6SUiQnM6DQdsvfsKZhRZtoBNSj9MfdmwotVZSOEZJ+IjZ1FR9ChvMWayO9ns/o8LgoQxr1ZXeg== dependencies: - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-tools" "12.3.2" chalk "^4.1.2" execa "^5.0.0" fast-xml-parser "^4.0.12" glob "^7.1.3" ora "^5.4.1" -"@react-native-community/cli-plugin-metro@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.7.tgz#2e8a9deb30b40495c5c1347a1837a824400fa00f" - integrity sha512-0WhgoBVGF1f9jXcuagQmtxpwpfP+2LbLZH4qMyo6OtYLWLG13n2uRep+8tdGzfNzl1bIuUTeE9yZSAdnf9LfYQ== - dependencies: - "@react-native-community/cli-server-api" "11.3.7" - "@react-native-community/cli-tools" "11.3.7" - chalk "^4.1.2" - execa "^5.0.0" - metro "0.76.8" - metro-config "0.76.8" - metro-core "0.76.8" - metro-react-native-babel-transformer "0.76.8" - metro-resolver "0.76.8" - metro-runtime "0.76.8" - readline "^1.3.0" +"@react-native-community/cli-plugin-metro@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.2.tgz#7db7dc8939b821b9aeebdd5ee3293f3a0201a2ea" + integrity sha512-FpFBwu+d2E7KRhYPTkKvQsWb2/JKsJv+t1tcqgQkn+oByhp+qGyXBobFB8/R3yYvRRDCSDhS+atWTJzk9TjM8g== -"@react-native-community/cli-server-api@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-11.3.7.tgz#2cce54b3331c9c51b9067129c297ab2e9a142216" - integrity sha512-yoFyGdvR3HxCnU6i9vFqKmmSqFzCbnFSnJ29a+5dppgPRetN+d//O8ard/YHqHzToFnXutAFf2neONn23qcJAg== +"@react-native-community/cli-server-api@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-12.3.2.tgz#11df4e20ed72d59cf22adf77bd30aff3d6e70dc9" + integrity sha512-iwa7EO9XFA/OjI5pPLLpI/6mFVqv8L73kNck3CNOJIUCCveGXBKK0VMyOkXaf/BYnihgQrXh+x5cxbDbggr7+Q== dependencies: - "@react-native-community/cli-debugger-ui" "11.3.7" - "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-debugger-ui" "12.3.2" + "@react-native-community/cli-tools" "12.3.2" compression "^1.7.1" connect "^3.6.5" errorhandler "^1.5.1" @@ -2333,10 +2449,10 @@ serve-static "^1.13.1" ws "^7.5.1" -"@react-native-community/cli-tools@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-11.3.7.tgz#37aa7efc7b4a1b7077d541f1d7bb11a2ab7b6ff2" - integrity sha512-peyhP4TV6Ps1hk+MBHTFaIR1eI3u+OfGBvr5r0wPwo3FAJvldRinMgcB/TcCcOBXVORu7ba1XYjkubPeYcqAyA== +"@react-native-community/cli-tools@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-12.3.2.tgz#d3362b04fba3f73ec82c5a493696b575acfb420c" + integrity sha512-nDH7vuEicHI2TI0jac/DjT3fr977iWXRdgVAqPZFFczlbs7A8GQvEdGnZ1G8dqRUmg+kptw0e4hwczAOG89JzQ== dependencies: appdirsjs "^1.2.4" chalk "^4.1.2" @@ -2347,35 +2463,37 @@ ora "^5.4.1" semver "^7.5.2" shell-quote "^1.7.3" + sudo-prompt "^9.0.0" -"@react-native-community/cli-types@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-11.3.7.tgz#12fe7cff3da08bd27e11116531b2e001939854b9" - integrity sha512-OhSr/TiDQkXjL5YOs8+hvGSB+HltLn5ZI0+A3DCiMsjUgTTsYh+Z63OtyMpNjrdCEFcg0MpfdU2uxstCS6Dc5g== +"@react-native-community/cli-types@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-12.3.2.tgz#0551c553c87701faae580097d7786dfff8ec2ef4" + integrity sha512-9D0UEFqLW8JmS16mjHJxUJWX8E+zJddrHILSH8AJHZ0NNHv4u2DXKdb0wFLMobFxGNxPT+VSOjc60fGvXzWHog== dependencies: joi "^17.2.1" -"@react-native-community/cli@11.3.7": - version "11.3.7" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-11.3.7.tgz#564c0054269d8385fa9d301750b2e56dbb5c0cc9" - integrity sha512-Ou8eDlF+yh2rzXeCTpMPYJ2fuqsusNOhmpYPYNQJQ2h6PvaF30kPomflgRILems+EBBuggRtcT+I+1YH4o/q6w== - dependencies: - "@react-native-community/cli-clean" "11.3.7" - "@react-native-community/cli-config" "11.3.7" - "@react-native-community/cli-debugger-ui" "11.3.7" - "@react-native-community/cli-doctor" "11.3.7" - "@react-native-community/cli-hermes" "11.3.7" - "@react-native-community/cli-plugin-metro" "11.3.7" - "@react-native-community/cli-server-api" "11.3.7" - "@react-native-community/cli-tools" "11.3.7" - "@react-native-community/cli-types" "11.3.7" +"@react-native-community/cli@12.3.2": + version "12.3.2" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-12.3.2.tgz#002ae3683b9fe6b0a83a837f41d9db541ea7667f" + integrity sha512-WgoUWwLDcf/G1Su2COUUVs3RzAwnV/vUTdISSpAUGgSc57mPabaAoUctKTnfYEhCnE3j02k3VtaVPwCAFRO3TQ== + dependencies: + "@react-native-community/cli-clean" "12.3.2" + "@react-native-community/cli-config" "12.3.2" + "@react-native-community/cli-debugger-ui" "12.3.2" + "@react-native-community/cli-doctor" "12.3.2" + "@react-native-community/cli-hermes" "12.3.2" + "@react-native-community/cli-plugin-metro" "12.3.2" + "@react-native-community/cli-server-api" "12.3.2" + "@react-native-community/cli-tools" "12.3.2" + "@react-native-community/cli-types" "12.3.2" chalk "^4.1.2" commander "^9.4.1" + deepmerge "^4.3.0" execa "^5.0.0" find-up "^4.1.0" fs-extra "^8.1.0" graceful-fs "^4.1.3" - prompts "^2.4.0" + prompts "^2.4.2" semver "^7.5.2" "@react-native-community/eslint-config@^3.2.0": @@ -2402,15 +2520,70 @@ resolved "https://registry.yarnpkg.com/@react-native-community/eslint-plugin/-/eslint-plugin-1.3.0.tgz#9e558170c106bbafaa1ef502bd8e6d4651012bf9" integrity sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg== -"@react-native/assets-registry@^0.72.0": - version "0.72.0" - resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.72.0.tgz#c82a76a1d86ec0c3907be76f7faf97a32bbed05d" - integrity sha512-Im93xRJuHHxb1wniGhBMsxLwcfzdYreSZVQGDoMJgkd6+Iky61LInGEHnQCTN0fKNYF1Dvcofb4uMmE1RQHXHQ== +"@react-native/assets-registry@0.73.1", "@react-native/assets-registry@~0.73.1": + version "0.73.1" + resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.73.1.tgz#e2a6b73b16c183a270f338dc69c36039b3946e85" + integrity sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg== + +"@react-native/babel-plugin-codegen@0.73.3": + version "0.73.3" + resolved "https://registry.yarnpkg.com/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.3.tgz#6bf135322b89264342c80778ee6bb697f968f773" + integrity sha512-+zQrDDbz6lB48LyzFHxNCgXDCBHH+oTRdXAjikRcBUdeG9St9ABbYFLtb799zSxLOrCqFVyXqhJR2vlgLLEbcg== + dependencies: + "@react-native/codegen" "0.73.2" -"@react-native/codegen@^0.72.7": - version "0.72.8" - resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.72.8.tgz#0593f628e1310f430450a9479fbb4be35e7b63d6" - integrity sha512-jQCcBlXV7B7ap5VlHhwIPieYz89yiRgwd2FPUBu+unz+kcJ6pAiB2U8RdLDmyIs8fiWd+Vq1xxaWs4TR329/ng== +"@react-native/babel-preset@0.73.20", "@react-native/babel-preset@^0.73.18": + version "0.73.20" + resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.73.20.tgz#65ab68cce16bb222bb1faece498abb6f7b1d5db0" + integrity sha512-fU9NqkusbfFq71l4BWQfqqD/lLcLC0MZ++UYgieA3j8lIEppJTLVauv2RwtD2yltBkjebgYEC5Rwvt1l0MUBXw== + dependencies: + "@babel/core" "^7.20.0" + "@babel/plugin-proposal-async-generator-functions" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.18.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" + "@babel/plugin-proposal-numeric-separator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.20.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.18.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.20.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.20.0" + "@babel/plugin-transform-flow-strip-types" "^7.20.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.5.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + "@react-native/babel-plugin-codegen" "0.73.3" + babel-plugin-transform-flow-enums "^0.0.2" + react-refresh "^0.14.0" + +"@react-native/codegen@0.73.2": + version "0.73.2" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.73.2.tgz#58af4e4c3098f0e6338e88ec64412c014dd51519" + integrity sha512-lfy8S7umhE3QLQG5ViC4wg5N1Z+E6RnaeIw8w1voroQsXXGPB72IBozh8dAHR3+ceTxIU0KX3A8OpJI8e1+HpQ== dependencies: "@babel/parser" "^7.20.0" flow-parser "^0.206.0" @@ -2420,35 +2593,78 @@ mkdirp "^0.5.1" nullthrows "^1.1.1" -"@react-native/gradle-plugin@^0.72.11": - version "0.72.11" - resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.72.11.tgz#c063ef12778706611de7a1e42b74b14d9405fb9f" - integrity sha512-P9iRnxiR2w7EHcZ0mJ+fmbPzMby77ZzV6y9sJI3lVLJzF7TLSdbwcQyD3lwMsiL+q5lKUHoZJS4sYmih+P2HXw== +"@react-native/community-cli-plugin@0.73.14": + version "0.73.14" + resolved "https://registry.yarnpkg.com/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz#e7767df11a8f54fd84ebff36d8962ef733c8143d" + integrity sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg== + dependencies: + "@react-native-community/cli-server-api" "12.3.2" + "@react-native-community/cli-tools" "12.3.2" + "@react-native/dev-middleware" "0.73.7" + "@react-native/metro-babel-transformer" "0.73.14" + chalk "^4.0.0" + execa "^5.1.1" + metro "^0.80.3" + metro-config "^0.80.3" + metro-core "^0.80.3" + node-fetch "^2.2.0" + readline "^1.3.0" + +"@react-native/debugger-frontend@0.73.3": + version "0.73.3" + resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz#033757614d2ada994c68a1deae78c1dd2ad33c2b" + integrity sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw== + +"@react-native/dev-middleware@0.73.7", "@react-native/dev-middleware@^0.73.6": + version "0.73.7" + resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.73.7.tgz#61d2bf08973d9a537fa3f2a42deeb13530d721ae" + integrity sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg== + dependencies: + "@isaacs/ttlcache" "^1.4.1" + "@react-native/debugger-frontend" "0.73.3" + chrome-launcher "^0.15.2" + chromium-edge-launcher "^1.0.0" + connect "^3.6.5" + debug "^2.2.0" + node-fetch "^2.2.0" + open "^7.0.3" + serve-static "^1.13.1" + temp-dir "^2.0.0" + +"@react-native/gradle-plugin@0.73.4": + version "0.73.4" + resolved "https://registry.yarnpkg.com/@react-native/gradle-plugin/-/gradle-plugin-0.73.4.tgz#aa55784a8c2b471aa89934db38c090d331baf23b" + integrity sha512-PMDnbsZa+tD55Ug+W8CfqXiGoGneSSyrBZCMb5JfiB3AFST3Uj5e6lw8SgI/B6SKZF7lG0BhZ6YHZsRZ5MlXmg== -"@react-native/js-polyfills@^0.72.1": - version "0.72.1" - resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.72.1.tgz#905343ef0c51256f128256330fccbdb35b922291" - integrity sha512-cRPZh2rBswFnGt5X5EUEPs0r+pAsXxYsifv/fgy9ZLQokuT52bPH+9xjDR+7TafRua5CttGW83wP4TntRcWNDA== +"@react-native/js-polyfills@0.73.1": + version "0.73.1" + resolved "https://registry.yarnpkg.com/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz#730b0a7aaab947ae6f8e5aa9d995e788977191ed" + integrity sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g== + +"@react-native/metro-babel-transformer@0.73.14": + version "0.73.14" + resolved "https://registry.yarnpkg.com/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz#a4ee02c729216e4ab5b7c7aa28abbfe8e0a943a8" + integrity sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw== + dependencies: + "@babel/core" "^7.20.0" + "@react-native/babel-preset" "0.73.20" + hermes-parser "0.15.0" + nullthrows "^1.1.1" "@react-native/normalize-color@^2.0.0", "@react-native/normalize-color@^2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91" integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA== -"@react-native/normalize-colors@*": - version "0.74.1" - resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.74.1.tgz#6e8ccf99954728dcd3cfe0d56e758ee5050a7bea" - integrity sha512-r+bTRs6pImqE3fx4h7bPzH2sOWSrnSHF/RJ7d00pNUj2P6ws3DdhS7WV+/7YosZkloYQfkiIkK3pIHvcYn665w== - -"@react-native/normalize-colors@^0.72.0": - version "0.72.0" - resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.72.0.tgz#14294b7ed3c1d92176d2a00df48456e8d7d62212" - integrity sha512-285lfdqSXaqKuBbbtP9qL2tDrfxdOFtIMvkKadtleRQkdOxx+uzGvFr82KHmc/sSiMtfXGp7JnFYWVh4sFl7Yw== +"@react-native/normalize-colors@0.73.2", "@react-native/normalize-colors@^0.73.0": + version "0.73.2" + resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz#cc8e48fbae2bbfff53e12f209369e8d2e4cf34ec" + integrity sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w== -"@react-native/virtualized-lists@^0.72.8": - version "0.72.8" - resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz#a2c6a91ea0f1d40eb5a122fb063daedb92ed1dc3" - integrity sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw== +"@react-native/virtualized-lists@0.73.4": + version "0.73.4" + resolved "https://registry.yarnpkg.com/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz#640e594775806f63685435b5d9c3d05c378ccd8c" + integrity sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog== dependencies: invariant "^2.2.4" nullthrows "^1.1.1" @@ -2504,6 +2720,81 @@ dependencies: nanoid "^3.1.23" +"@remix-run/node@^1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-1.19.3.tgz#d27e2f742fc45379525cb3fca466a883ca06d6c9" + integrity sha512-z5qrVL65xLXIUpU4mkR4MKlMeKARLepgHAk4W5YY3IBXOreRqOGUC70POViYmY7x38c2Ia1NwqL80H+0h7jbMw== + dependencies: + "@remix-run/server-runtime" "1.19.3" + "@remix-run/web-fetch" "^4.3.6" + "@remix-run/web-file" "^3.0.3" + "@remix-run/web-stream" "^1.0.4" + "@web3-storage/multipart-parser" "^1.0.0" + abort-controller "^3.0.0" + cookie-signature "^1.1.0" + source-map-support "^0.5.21" + stream-slice "^0.1.2" + +"@remix-run/router@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" + integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== + +"@remix-run/server-runtime@1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-1.19.3.tgz#206b55337c266c5bc254878f8ff3cd5677cc60fb" + integrity sha512-KzQ+htUsKqpBgKE2tWo7kIIGy3MyHP58Io/itUPvV+weDjApwr9tQr9PZDPA3yAY6rAzLax7BU0NMSYCXWFY5A== + dependencies: + "@remix-run/router" "1.7.2" + "@types/cookie" "^0.4.1" + "@web3-storage/multipart-parser" "^1.0.0" + cookie "^0.4.1" + set-cookie-parser "^2.4.8" + source-map "^0.7.3" + +"@remix-run/web-blob@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-blob/-/web-blob-3.1.0.tgz#e0c669934c1eb6028960047e57a13ed38bbfb434" + integrity sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g== + dependencies: + "@remix-run/web-stream" "^1.1.0" + web-encoding "1.1.5" + +"@remix-run/web-fetch@^4.3.6": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@remix-run/web-fetch/-/web-fetch-4.4.2.tgz#ce7aedef72cc26e15060e8cf84674029f92809b6" + integrity sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA== + dependencies: + "@remix-run/web-blob" "^3.1.0" + "@remix-run/web-file" "^3.1.0" + "@remix-run/web-form-data" "^3.1.0" + "@remix-run/web-stream" "^1.1.0" + "@web3-storage/multipart-parser" "^1.0.0" + abort-controller "^3.0.0" + data-uri-to-buffer "^3.0.1" + mrmime "^1.0.0" + +"@remix-run/web-file@^3.0.3", "@remix-run/web-file@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-file/-/web-file-3.1.0.tgz#07219021a2910e90231bc30ca1ce693d0e9d3825" + integrity sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ== + dependencies: + "@remix-run/web-blob" "^3.1.0" + +"@remix-run/web-form-data@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-form-data/-/web-form-data-3.1.0.tgz#47f9ad8ce8bf1c39ed83eab31e53967fe8e3df6a" + integrity sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A== + dependencies: + web-encoding "1.1.5" + +"@remix-run/web-stream@^1.0.4", "@remix-run/web-stream@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@remix-run/web-stream/-/web-stream-1.1.0.tgz#b93a8f806c2c22204930837c44d81fdedfde079f" + integrity sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA== + dependencies: + web-streams-polyfill "^3.1.1" + "@segment/loosely-validate-event@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz#87dfc979e5b4e7b82c5f1d8b722dfd5d77644681" @@ -2610,6 +2901,11 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -2672,11 +2968,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== -"@types/qs@^6.9.7": - version "6.9.11" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda" - integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ== - "@types/react@~18.2.14": version "18.2.47" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40" @@ -2718,13 +3009,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^16.0.0": - version "16.0.9" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.9.tgz#ba506215e45f7707e6cbcaf386981155b7ab956e" - integrity sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA== - dependencies: - "@types/yargs-parser" "*" - "@types/yargs@^17.0.8": version "17.0.32" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" @@ -2845,6 +3129,11 @@ "@urql/core" ">=2.3.1" wonka "^4.0.14" +"@web3-storage/multipart-parser@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" + integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== + "@xmldom/xmldom@^0.8.8": version "0.8.10" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" @@ -2867,6 +3156,11 @@ resolved "https://registry.yarnpkg.com/@zxcvbn-ts/language-common/-/language-common-3.0.4.tgz#fa1d2a42f8c8a589555859795da90d6b8027b7c4" integrity sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw== +"@zxing/text-encoding@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -3049,12 +3343,7 @@ application-config-path@^0.1.0: resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.1.tgz#8b5ac64ff6afdd9bd70ce69f6f64b6998f5f756e" integrity sha512-zy9cHePtMP0YhwG+CfHm0bgwdnga2X3gZexpdCwEj//dpb+TKajtiC8REEUJUSq6Ab4f9cgNy2l8ObXzCXFkEw== -arg@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0" - integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg== - -arg@^5.0.2: +arg@5.0.2, arg@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== @@ -3161,11 +3450,6 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^3.2.2: - version "3.2.5" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" - integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== - asynciterator.prototype@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" @@ -3183,14 +3467,14 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -autoprefixer@^10.4.16: - version "10.4.16" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" - integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== +autoprefixer@^10.4.17: + version "10.4.17" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be" + integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg== dependencies: - browserslist "^4.21.10" - caniuse-lite "^1.0.30001538" - fraction.js "^4.3.6" + browserslist "^4.22.2" + caniuse-lite "^1.0.30001578" + fraction.js "^4.3.7" normalize-range "^0.1.2" picocolors "^1.0.0" postcss-value-parser "^4.2.0" @@ -3248,17 +3532,6 @@ babel-plugin-macros@^3.1.0: cosmiconfig "^7.0.0" resolve "^1.19.0" -babel-plugin-module-resolver@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.0.tgz#2b7fc176bd55da25f516abf96015617b4f70fc73" - integrity sha512-g0u+/ChLSJ5+PzYwLwP8Rp8Rcfowz58TJNCe+L/ui4rpzE/mg//JVX0EWBUYoxaextqnwuGHzfGp2hh0PPV25Q== - dependencies: - find-babel-config "^2.0.0" - glob "^8.0.3" - pkg-up "^3.1.0" - reselect "^4.1.7" - resolve "^1.22.1" - babel-plugin-polyfill-corejs2@^0.4.7: version "0.4.7" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz#679d1b94bf3360f7682e11f2cb2708828a24fe8c" @@ -3318,19 +3591,20 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-expo@~9.5.2: - version "9.5.2" - resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-9.5.2.tgz#5ed1756c8434ca972d7a940e4f13570a283641df" - integrity sha512-hU1G1TDiikuXV6UDZjPnX+WdbjbtidDiYhftMEVrZQSst45pDPVBWbM41TUKrpJMwv4FypsLzK+378gnMPRVWQ== +babel-preset-expo@~10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-10.0.1.tgz#a0e7ad0119f46e58cb3f0738c3ca0c6e97b69c11" + integrity sha512-uWIGmLfbP3dS5+8nesxaW6mQs41d4iP7X82ZwRdisB/wAhKQmuJM9Y1jQe4006uNYkw6Phf2TT03ykLVro7KuQ== dependencies: "@babel/plugin-proposal-decorators" "^7.12.9" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-object-rest-spread" "^7.12.13" - "@babel/plugin-transform-react-jsx" "^7.12.17" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.12.13" + "@babel/plugin-transform-parameters" "^7.22.15" "@babel/preset-env" "^7.20.0" - babel-plugin-module-resolver "^5.0.0" + "@babel/preset-react" "^7.22.15" + "@react-native/babel-preset" "^0.73.18" babel-plugin-react-native-web "~0.18.10" - metro-react-native-babel-preset "0.76.8" + react-refresh "0.14.0" babel-preset-fbjs@^3.4.0: version "3.4.0" @@ -3383,7 +3657,7 @@ base-64@1.0.0: resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a" integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg== -base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: +base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3419,24 +3693,6 @@ blueimp-md5@^2.10.0: resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -body-parser@^1.20.1: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== - 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.11.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - bplist-creator@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" @@ -3487,7 +3743,7 @@ browser-tabs-lock@1.2.15: dependencies: lodash ">=4.17.21" -browserslist@^4.21.10, browserslist@^4.22.2: +browserslist@^4.22.2: version "4.22.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -3545,11 +3801,6 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - cacache@^15.3.0: version "15.3.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" @@ -3627,10 +3878,10 @@ camelize@^1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: - version "1.0.30001576" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" - integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== +caniuse-lite@^1.0.30001565, caniuse-lite@^1.0.30001578: + version "1.0.30001580" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz#e3c76bc6fe020d9007647044278954ff8cd17d1e" + integrity sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA== chalk@^2.0.1, chalk@^2.4.2: version "2.4.2" @@ -3692,6 +3943,28 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chrome-launcher@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" + integrity sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + +chromium-edge-launcher@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz#0443083074715a13c669530b35df7bfea33b1509" + integrity sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + mkdirp "^1.0.4" + rimraf "^3.0.2" + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -3860,11 +4133,6 @@ commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3877,11 +4145,6 @@ compare-urls@^2.0.0: dependencies: normalize-url "^2.0.1" -compare-versions@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - component-type@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.2.2.tgz#4458ecc0c1871efc6288bfaff0cbdab08141d079" @@ -3922,11 +4185,6 @@ connect@^3.6.5, connect@^3.7.0: parseurl "~1.3.3" utils-merge "1.0.1" -content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - convert-source-map@^1.5.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" @@ -3937,6 +4195,16 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-signature@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.1.tgz#790dea2cce64638c7ae04d9fabed193bd7ccf3b4" + integrity sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + copy-to-clipboard@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" @@ -4100,6 +4368,11 @@ dag-map@~1.0.0: resolved "https://registry.yarnpkg.com/dag-map/-/dag-map-1.0.2.tgz#e8379f041000ed561fc515475c1ed2c85eece8d7" integrity sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw== +data-uri-to-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + data-urls@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -4114,7 +4387,7 @@ dayjs@^1.8.15: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== -debug@2.6.9, debug@^2.2.0: +debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -4237,14 +4510,14 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -deprecated-react-native-prop-types@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-4.1.0.tgz#8ed03a64c21b7fbdd2d000957b6838d4f38d2c66" - integrity sha512-WfepZHmRbbdTvhcolb8aOKEvQdcmTMn5tKLbqbXmkBvjFjRVWAYqsXk/DBsV8TZxws8SdGHLuHaJrHSQUPRdfw== +deprecated-react-native-prop-types@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz#02a12f090da7bd9e8c3ac53c31cf786a1315d302" + integrity sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ== dependencies: - "@react-native/normalize-colors" "*" - invariant "*" - prop-types "*" + "@react-native/normalize-colors" "^0.73.0" + invariant "^2.2.4" + prop-types "^15.8.1" dequal@2.0.3: version "2.0.3" @@ -4371,10 +4644,10 @@ env-editor@^0.4.1: resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861" integrity sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA== -envinfo@^7.7.2: - version "7.11.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.0.tgz#c3793f44284a55ff8c82faf1ffd91bc6478ea01f" - integrity sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg== +envinfo@^7.10.0: + version "7.11.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.1.tgz#2ffef77591057081b0129a8fd8cf6118da1b94e1" + integrity sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg== eol@^0.9.1: version "0.9.1" @@ -4757,7 +5030,7 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^5.0.0: +execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -4788,179 +5061,156 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -expo-application@~5.3.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.3.1.tgz#074bbfc6bb5d65ae74a67f5288fa3eb582237e53" - integrity sha512-HR2+K+Hm33vLw/TfbFaHrvUbRRNRco8R+3QaCKy7eJC2LFfT05kZ15ynGaKfB5DJ/oqPV3mxXVR/EfwmE++hoA== +expo-application@~5.8.0: + version "5.8.3" + resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.8.3.tgz#43991bd81d05c987b07b2f430c036cda1572bc62" + integrity sha512-IISxzpPX+Xe4ynnwX8yY52T6dm1g9sME1GCj4lvUlrdc5xeTPM6U35x7Wj82V7lLWBaVGe+/Tg9EeKqfylCEwA== -expo-asset@~8.10.1: - version "8.10.1" - resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-8.10.1.tgz#a7e8cf1c555ab8f844599822cb084fee95a93644" - integrity sha512-5VMTESxgY9GBsspO/esY25SKEa7RyascVkLe/OcL1WgblNFm7xCCEEUIW8VWS1nHJQGYxpMZPr3bEfjMpdWdyA== +expo-asset@~9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/expo-asset/-/expo-asset-9.0.2.tgz#e8a6b6da356d5fc97955599d2fa49af78c7f0bfd" + integrity sha512-PzYKME1MgUOoUvwtdzhAyXkjXOXGiSYqGKG/MsXwWr0Ef5wlBaBm2DCO9V6KYbng5tBPFu6hTjoRNil1tBOSow== dependencies: + "@react-native/assets-registry" "~0.73.1" blueimp-md5 "^2.10.0" - expo-constants "~14.4.2" - expo-file-system "~15.4.0" + expo-constants "~15.4.0" + expo-file-system "~16.0.0" invariant "^2.2.4" md5-file "^3.2.3" - path-browserify "^1.0.0" - url-parse "^1.5.9" -expo-auth-session@~5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/expo-auth-session/-/expo-auth-session-5.0.2.tgz#1e9e6e7ef4d6784e77d770f0d3ea3a4adfc0e0b4" - integrity sha512-hzuIGATiyZ4ICuzSnCTTQLUA74eHGd1aaPydsSAQEAkMnNT2bMoIYLq1rp971xF+eqWz0lzMVboRYTnxuvEKJg== +expo-auth-session@~5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/expo-auth-session/-/expo-auth-session-5.4.0.tgz#caae0bae4eeae649d320124ba8cd623c40c6cf0a" + integrity sha512-ZwjPMsMgCqdMi+vnhDbtjOAF12Y9+y1bYvorn/jQs47aFg6yeIRHycSOM/WL4hpFr+fAxycP3mIJeHVYfF3zuQ== dependencies: - expo-constants "~14.4.2" - expo-crypto "~12.4.0" - expo-linking "~5.0.0" - expo-web-browser "~12.3.0" + expo-application "~5.8.0" + expo-constants "~15.4.0" + expo-crypto "~12.8.0" + expo-linking "~6.2.0" + expo-web-browser "~12.8.0" invariant "^2.2.4" - qs "^6.11.0" -expo-constants@~14.4.2: - version "14.4.2" - resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.4.2.tgz#cac5e8b524069545739b8d8595ce96cc5be6578c" - integrity sha512-nOB122DOAjk+KrJT69lFQAoYVQGQjFHSigCPVBzVdko9S1xGsfiOH9+X5dygTsZTIlVLpQJDdmZ7ONiv3i+26w== +expo-constants@~15.4.0, expo-constants@~15.4.3: + version "15.4.5" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-15.4.5.tgz#81756a4c4e1c020f840a419cd86a124a6d1fb35b" + integrity sha512-1pVVjwk733hbbIjtQcvUFCme540v4gFemdNlaxM2UXKbfRCOh2hzgKN5joHMOysoXQe736TTUrRj7UaZI5Yyhg== dependencies: - "@expo/config" "~8.1.0" - uuid "^3.3.2" + "@expo/config" "~8.5.0" -expo-crypto@~12.4.0: - version "12.4.1" - resolved "https://registry.yarnpkg.com/expo-crypto/-/expo-crypto-12.4.1.tgz#f3ee7156fd8167c3c3304b6e21dcfb0dc8a44bba" - integrity sha512-/en03oPNAX6gP0bKpwA1EyLBnGG9uv0+Q7uvGYyOXaQQEvj31a+8cEvNPkv75x6GuK1hcaBfO25RtX9AGOMwVA== +expo-crypto@~12.8.0: + version "12.8.0" + resolved "https://registry.yarnpkg.com/expo-crypto/-/expo-crypto-12.8.0.tgz#dc06793bd569401995225cb2f8783e49e3090ed8" + integrity sha512-67CoxXz+b4VU1zMo/2kUp+9t6TiVs8HvCvHsW8zoLLAZkVNa3YW1l0arLtQ4oR4HQpEr1i9rAZhP0/mvo+fg5A== dependencies: base64-js "^1.3.0" -expo-file-system@~15.4.0, expo-file-system@~15.4.5: - version "15.4.5" - resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-15.4.5.tgz#3ef68583027ff0e2fb9eca7a22b3caff6cfc550d" - integrity sha512-xy61KaTaDgXhT/dllwYDHm3ch026EyO8j4eC6wSVr/yE12MMMxAC09yGwy4f7kkOs6ztGVQF5j7ldRzNLN4l0Q== - dependencies: - uuid "^3.4.0" +expo-file-system@~16.0.0, expo-file-system@~16.0.5: + version "16.0.5" + resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.5.tgz#7ade3b1295d47a38a2236c5b121069d1bf27a526" + integrity sha512-JpKMbKfwTaMCbwUwq7MwcSbPR7r+IqZEL3RFam3ClPHDtKLnlEoywREeaDsWjSZb7dS25hG3WqXspfTuugCDvg== -expo-font@~11.4.0: - version "11.4.0" - resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-11.4.0.tgz#e2d31c0bb76ba3c37c2d84703a49aeafc3afef28" - integrity sha512-nkmezCFD7gR/I6R+e3/ry18uEfF8uYrr6h+PdBJu+3dawoLOpo+wFb/RG9bHUekU1/cPanR58LR7G5MEMKHR2w== +expo-font@~11.10.2: + version "11.10.2" + resolved "https://registry.yarnpkg.com/expo-font/-/expo-font-11.10.2.tgz#233195c4fa0321763bd89bd7919bf7271a439c3f" + integrity sha512-AE0Q0LiWiVosQ/jlKUPoWoob7p3GwYM2xmLoUkuopO9RYh9NL1hZKHiMKcWBZyDG8Gww1GtBQwh7ZREST8+jjQ== dependencies: fontfaceobserver "^2.1.0" -expo-head@0.0.20: - version "0.0.20" - resolved "https://registry.yarnpkg.com/expo-head/-/expo-head-0.0.20.tgz#a99d9f349110cae878e10a8d5a31749155fb5ab1" - integrity sha512-K0ETFOp/I+Td1T40D8k+Nlk8zCtvUFKTVYiwUhLoCCPf4dGC0zXv/noJLgyZ8jZ+5FJLlrSTpk2Gm9bxJfqkLw== - dependencies: - react-helmet-async "^1.3.0" - -expo-keep-awake@~12.3.0: - version "12.3.0" - resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.3.0.tgz#c42449ae19c993274ddc43aafa618792b6aec408" - integrity sha512-ujiJg1p9EdCOYS05jh5PtUrfiZnK0yyLy+UewzqrjUqIT8eAGMQbkfOn3C3fHE7AKd5AefSMzJnS3lYZcZYHDw== +expo-keep-awake@~12.8.2: + version "12.8.2" + resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.8.2.tgz#6cfdf8ad02b5fa130f99d4a1eb98e459d5b4332e" + integrity sha512-uiQdGbSX24Pt8nGbnmBtrKq6xL/Tm3+DuDRGBk/3ZE/HlizzNosGRIufIMJ/4B4FRw4dw8KU81h2RLuTjbay6g== -expo-linking@~5.0.0, expo-linking@~5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-5.0.2.tgz#273c9dfec0c5542a13638bd422ef9acbf4638bc5" - integrity sha512-SPQus0+tYGx9c69Uw4wmdo3rkKX8vRT1vyJz/mvkpSlZN986s0NmP/V0M5vDv5Zv2qZzVdqJyuITFe0Pg5aI+A== +expo-linking@~6.2.0, expo-linking@~6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-6.2.2.tgz#b7e148068ae49fd9ad814428c16fdf7a236e8aca" + integrity sha512-FEe6lP4f7xFT/vjoHRG+tt6EPVtkEGaWNK1smpaUevmNdyCJKqW0PDB8o8sfG6y7fly8ULe8qg3HhKh5J7aqUQ== dependencies: - "@types/qs" "^6.9.7" - expo-constants "~14.4.2" + expo-constants "~15.4.3" invariant "^2.2.4" - qs "^6.11.0" - url-parse "^1.5.9" -expo-modules-autolinking@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.5.1.tgz#363f90c172769ce12bf56c7be9ca0897adfc7a81" - integrity sha512-yt5a1VCp2BF9CrsO689PCD5oXKP14MMhnOanQMvDn4BDpURYfzAlDVGC5fZrNQKtwn/eq3bcrxIwZ7D9QjVVRg== +expo-modules-autolinking@1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.10.2.tgz#a413235941c2f7167f5e5d5b66807f7417a5a8ea" + integrity sha512-OEeoz0+zGx5EJwGtDm9pSywCr+gUCaisZV0mNkK7V3fuRl+EVPBSsI+957JwAc4ZxVps95jy28eLcRRtQ33yVg== dependencies: - "@expo/config" "~8.1.0" + "@expo/config" "~8.5.0" chalk "^4.1.0" commander "^7.2.0" fast-glob "^3.2.5" find-up "^5.0.0" fs-extra "^9.1.0" -expo-modules-core@1.5.12: - version "1.5.12" - resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.5.12.tgz#07eb4de4bf25a3ec3e1924403e73d13c656613fd" - integrity sha512-mY4wTDU458dhwk7IVxLNkePlYXjs9BTgk4NQHBUXf0LapXsvr+i711qPZaFNO4egf5qq6fQV+Yfd/KUguHstnQ== +expo-modules-core@1.11.8: + version "1.11.8" + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.11.8.tgz#b0bdb31e70e1e570b70a9613f4b6392306c99a80" + integrity sha512-rlctE3nCNLCGv3LosGQNaTuwGrr2SyQA+hOgci/0l+VRc0gFNtvl0gskph9C0tnN1jzBeb8rRZQYVj5ih1yxcA== dependencies: - compare-versions "^3.4.0" invariant "^2.2.4" -expo-router@^2.0.0: - version "2.0.14" - resolved "https://registry.yarnpkg.com/expo-router/-/expo-router-2.0.14.tgz#6c05fc9620d3d3e94dfffdd013ad5dace1bc69e0" - integrity sha512-F5dP4WMe+zQLzlGSbC+NT9/67FGgwmUCJuPGD4BkK0eRt0uga+y53vQ59m5MCOebqE0N9Ukr/Jz8Num16iGqZA== +expo-router@^3.4.6: + version "3.4.6" + resolved "https://registry.yarnpkg.com/expo-router/-/expo-router-3.4.6.tgz#ef0c8b06e7e0e370ca8d0a1c4564996f788feb38" + integrity sha512-yxl0QE4KAqLmLyH8AxWsGSV3M34jsAE8X75cOB2oaK0+Pu9VHSUf6w3iRi93IiJ0rOUXm8jKrjhfhZOrrNh7EA== dependencies: - "@bacons/react-views" "^1.1.3" - "@expo/metro-runtime" "2.2.16" + "@expo/metro-runtime" "3.1.2" + "@expo/server" "^0.3.0" "@radix-ui/react-slot" "1.0.1" "@react-navigation/bottom-tabs" "~6.5.7" "@react-navigation/native" "~6.1.6" "@react-navigation/native-stack" "~6.9.12" - expo-head "0.0.20" - expo-splash-screen "~0.20.2" - query-string "7.1.3" + expo-splash-screen "0.26.4" react-helmet-async "^1.3.0" schema-utils "^4.0.1" - url "^0.11.0" -expo-splash-screen@~0.20.2, expo-splash-screen@~0.20.5: - version "0.20.5" - resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.20.5.tgz#ebeba3e3977606830f74f506ab2cc25042bb7efd" - integrity sha512-nTALYdjHpeEA30rdOWSguxn72ctv8WM8ptuUgpfRgsWyn4i6rwYds/rBXisX69XO5fg+XjHAQqijGx/b28+3tg== +expo-splash-screen@0.26.4, expo-splash-screen@~0.26.4: + version "0.26.4" + resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.26.4.tgz#bc1fb226c6eae03ee351a3ebe5521a37f868cbc7" + integrity sha512-2DwofTQ0FFQCsvDysm/msENsbyNsJiAJwK3qK/oXeizECAPqD7bK19J4z9kuEbr7ORPX9MLnTQYKl6kmX3keUg== dependencies: - "@expo/prebuild-config" "6.2.6" + "@expo/prebuild-config" "6.7.4" -expo-status-bar@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-1.6.0.tgz#e79ffdb9a84d2e0ec9a0dc7392d9ab364fefa9cf" - integrity sha512-e//Oi2WPdomMlMDD3skE4+1ZarKCJ/suvcB4Jo/nO427niKug5oppcPNYO+csR6y3ZglGuypS+3pp/hJ+Xp6fQ== +expo-status-bar@~1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-1.11.1.tgz#a11318741d361048c11db2b16c4364a79a74af30" + integrity sha512-ddQEtCOgYHTLlFUe/yH67dDBIoct5VIULthyT3LRJbEwdpzAgueKsX2FYK02ldh440V87PWKCamh7R9evk1rrg== -expo-system-ui@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/expo-system-ui/-/expo-system-ui-2.4.0.tgz#bf809172726cf661ab4f526129eb427bb5f6e4d4" - integrity sha512-uaBAYeQtFzyE8WVcch2V3G243xvOf7vJkLzrIJ3rJ8NA3uZRmxF0lMMe75oMrAaLVXyr1Z+ZE6UZwh7x49FuIg== +expo-system-ui@~2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/expo-system-ui/-/expo-system-ui-2.9.3.tgz#845c7615a6ede9d959dff1719df3d9392a43c080" + integrity sha512-RNFNBLJ9lhnjOGrHhtfDc15Ry/lF+SA4kwulmHzYGqaTeYvsL9q0K0+m9qmxuDdrbKJkuurvzvjVylDNnKNFVg== dependencies: "@react-native/normalize-color" "^2.0.0" debug "^4.3.2" -expo-web-browser@~12.3.0, expo-web-browser@~12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-12.3.2.tgz#45ac727a5d8462d7faa403ea2fa1db160ed8e4b5" - integrity sha512-ohBf+vnRnGzlTleY8EQ2XQU0vRdRwqMJtKkzM9MZRPDOK5QyJYPJjpk6ixGhxdeoUG2Ogj0InvhhgX9NUn4jkg== +expo-web-browser@~12.8.0, expo-web-browser@~12.8.2: + version "12.8.2" + resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-12.8.2.tgz#f34fb85c80031e0dddd4f9b9efd03cb60333b089" + integrity sha512-Mw8WoFMSADecNjtC4PZVsVj1/lYdxIAH1jOVV+F8v8SEWYxORWofoShfXg7oUxRLu0iUG8JETfO5y4m8+fOgdg== dependencies: compare-urls "^2.0.0" url "^0.11.0" -expo@~49.0.18: - version "49.0.21" - resolved "https://registry.yarnpkg.com/expo/-/expo-49.0.21.tgz#32a66b32d0a233879ec3afdec35fb63d2cc8a4c3" - integrity sha512-JpHL6V0yt8/fzsmkAdPdtsah+lU6Si4ac7MDklLYvzEil7HAFEsN/pf06wQ21ax4C+BL27hI6JJoD34tzXUCJA== +expo@~50.0.4: + version "50.0.4" + resolved "https://registry.yarnpkg.com/expo/-/expo-50.0.4.tgz#3ec5349a58b8b3264bc2f218d85b673beb352a99" + integrity sha512-8QWBvYZyKFd7pHxbtri8/ZITBR19QbrW2IkezAhs3ZOHR2kluSgNfyo9ojAe7GnOnE8hCB6Xe83Dbm0R3Ealhw== dependencies: "@babel/runtime" "^7.20.0" - "@expo/cli" "0.10.16" - "@expo/config" "8.1.2" - "@expo/config-plugins" "7.2.5" - "@expo/vector-icons" "^13.0.0" - babel-preset-expo "~9.5.2" - expo-application "~5.3.0" - expo-asset "~8.10.1" - expo-constants "~14.4.2" - expo-file-system "~15.4.5" - expo-font "~11.4.0" - expo-keep-awake "~12.3.0" - expo-modules-autolinking "1.5.1" - expo-modules-core "1.5.12" + "@expo/cli" "0.17.3" + "@expo/config" "8.5.4" + "@expo/config-plugins" "7.8.4" + "@expo/metro-config" "0.17.3" + "@expo/vector-icons" "^14.0.0" + babel-preset-expo "~10.0.1" + expo-asset "~9.0.2" + expo-file-system "~16.0.5" + expo-font "~11.10.2" + expo-keep-awake "~12.8.2" + expo-modules-autolinking "1.10.2" + expo-modules-core "1.11.8" fbemitter "^3.0.0" - invariant "^2.2.4" - md5-file "^3.2.3" - node-fetch "^2.6.7" - pretty-format "^26.5.2" - uuid "^3.4.0" + whatwg-url-without-unicode "8.0.0-3" extend@^3.0.1: version "3.0.2" @@ -4977,7 +5227,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-glob@^3.2.12, fast-glob@^3.2.5, fast-glob@^3.2.9: +fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -5010,6 +5260,13 @@ fast-xml-parser@^4.0.12: dependencies: strnum "^1.0.5" +fast-xml-parser@^4.2.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz#385cc256ad7bbc57b91515a38a22502a9e1fca0d" + integrity sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA== + dependencies: + strnum "^1.0.5" + fastest-levenshtein@1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" @@ -5091,14 +5348,6 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-babel-config@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-2.0.0.tgz#a8216f825415a839d0f23f4d18338a1cc966f701" - integrity sha512-dOKT7jvF3hGzlW60Gc3ONox/0rRZ/tz7WCil0bqA1In/3I8f1BctpXahRnEKDySZqci7u+dqq93sZST9fOJpFw== - dependencies: - json5 "^2.1.1" - path-exists "^4.0.0" - find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -5157,10 +5406,10 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -flow-enums-runtime@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.5.tgz#95884bfcc82edaf27eef7e1dd09732331cfbafbc" - integrity sha512-PSZF9ZuaZD03sT9YaIs0FrGJ7lSUw7rHZIex+73UYVXg46eL/wxN5PaVcPJFudE2cJu5f0fezitV5aBkLHPUOQ== +flow-enums-runtime@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" + integrity sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw== flow-parser@0.*: version "0.226.0" @@ -5210,7 +5459,7 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -fraction.js@^4.3.6: +fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== @@ -5399,7 +5648,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.7, glob@^7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5411,17 +5660,6 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5528,17 +5766,29 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -hermes-estree@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.12.0.tgz#8a289f9aee854854422345e6995a48613bac2ca8" - integrity sha512-+e8xR6SCen0wyAKrMT3UD0ZCCLymKhRgjEB5sS28rKiFir/fXgLoeRilRUssFCILmGHb+OvHDUlhxs0+IEyvQw== +hermes-estree@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.15.0.tgz#e32f6210ab18c7b705bdcb375f7700f2db15d6ba" + integrity sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ== -hermes-parser@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.12.0.tgz#114dc26697cfb41a6302c215b859b74224383773" - integrity sha512-d4PHnwq6SnDLhYl3LHNHvOg7nQ6rcI7QVil418REYksv0Mh3cEkHDcuhGxNQ3vgnLSLl4QSvDrFCwQNYdpWlzw== +hermes-estree@0.18.2: + version "0.18.2" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.18.2.tgz#fd450fa1659cf074ceaa2ddeeb21674f3b2342f3" + integrity sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ== + +hermes-parser@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.15.0.tgz#f611a297c2a2dbbfbce8af8543242254f604c382" + integrity sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q== + dependencies: + hermes-estree "0.15.0" + +hermes-parser@0.18.2: + version "0.18.2" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.18.2.tgz#50f15e2fcd559a48c68cd7af259d4292298bd14d" + integrity sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew== dependencies: - hermes-estree "0.12.0" + hermes-estree "0.18.2" hermes-profile-transformer@^0.0.6: version "0.0.6" @@ -5611,13 +5861,6 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -5724,7 +5967,7 @@ internal-slot@^1.0.5: hasown "^2.0.0" side-channel "^1.0.4" -invariant@*, invariant@^2.2.4: +invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -5746,6 +5989,14 @@ ipaddr.js@^1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -5860,7 +6111,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.10: +is-generator-function@^1.0.10, is-generator-function@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== @@ -5986,7 +6237,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -6257,7 +6508,7 @@ jest-environment-jsdom@^29.2.1: jest-util "^29.7.0" jsdom "^20.0.0" -jest-environment-node@^29.2.1, jest-environment-node@^29.7.0: +jest-environment-node@^29.6.3, jest-environment-node@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== @@ -6269,12 +6520,13 @@ jest-environment-node@^29.2.1, jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" -jest-expo@~49.0.0: - version "49.0.0" - resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-49.0.0.tgz#d1c91ddb1303f8666de47d45ba52174bff6fb241" - integrity sha512-nglYg6QPYSqCsrsOFiGosQi+m1rrqmYluPbFXNnXNEOrB2MvxMOgQJeWfMHDExHMX1ymLWX+7y8mYo6XVJpBJQ== +jest-expo@~50.0.1: + version "50.0.1" + resolved "https://registry.yarnpkg.com/jest-expo/-/jest-expo-50.0.1.tgz#4b180b2a06a2cad49c5c74cf8cbbd4387bb30621" + integrity sha512-osvA63UDLJ/v7MG9UHjU7WJ0oZ0Krq9UhXxm2s6rdOlnt85ARocyMU57RC0T0yzPN47C9Ref45sEeOIxoV4Mzg== dependencies: - "@expo/config" "~8.1.0" + "@expo/config" "~8.5.0" + "@expo/json-file" "^8.2.37" "@jest/create-cache-key-function" "^29.2.1" babel-jest "^29.2.1" find-up "^5.0.0" @@ -6284,6 +6536,7 @@ jest-expo@~49.0.0: json5 "^2.2.3" lodash "^4.17.19" react-test-renderer "18.2.0" + stacktrace-js "^2.0.2" jest-get-type@^29.6.3: version "29.6.3" @@ -6356,11 +6609,6 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^27.0.6: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" - integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== - jest-regex-util@^29.0.0, jest-regex-util@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" @@ -6470,18 +6718,6 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^27.2.0: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" - integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" @@ -6494,7 +6730,7 @@ jest-util@^29.7.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.2.1, jest-validate@^29.7.0: +jest-validate@^29.6.3, jest-validate@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== @@ -6542,16 +6778,7 @@ jest-watcher@^29.0.0, jest-watcher@^29.7.0: jest-util "^29.7.0" string-length "^4.0.1" -jest-worker@^27.2.0: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest-worker@^29.7.0: +jest-worker@^29.6.3, jest-worker@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== @@ -6576,7 +6803,7 @@ jimp-compact@0.16.1: resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.1.tgz#9582aea06548a2c1e04dd148d7c3ab92075aefa3" integrity sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww== -jiti@^1.18.2: +jiti@^1.19.1: version "1.21.0" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== @@ -6743,7 +6970,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@^2.1.1, json5@^2.2.2, json5@^2.2.3: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -6804,6 +7031,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lighthouse-logger@^1.0.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa" + integrity sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g== + dependencies: + debug "^2.6.9" + marky "^1.2.2" + lightningcss-darwin-arm64@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz#56ab071e932f845dbb7667f44f5b78441175a343" @@ -6989,6 +7224,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +marky@^1.2.2: + version "1.2.5" + resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" + integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== + md5-file@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-3.2.3.tgz#f9bceb941eca2214a4c0727f5e700314e770f06f" @@ -7019,11 +7259,6 @@ md5hex@^1.0.0: resolved "https://registry.yarnpkg.com/md5hex/-/md5hex-1.0.0.tgz#ed74b477a2ee9369f75efee2f08d5915e52a42e8" integrity sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -7049,62 +7284,60 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -metro-babel-transformer@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.76.8.tgz#5efd1027353b36b73706164ef09c290dceac096a" - integrity sha512-Hh6PW34Ug/nShlBGxkwQJSgPGAzSJ9FwQXhUImkzdsDgVu6zj5bx258J8cJVSandjNoQ8nbaHK6CaHlnbZKbyA== +metro-babel-transformer@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.80.5.tgz#a31bdafe22c63d2548c78a41fd902bf2a98af681" + integrity sha512-sxH6hcWCorhTbk4kaShCWsadzu99WBL4Nvq4m/sDTbp32//iGuxtAnUK+ZV+6IEygr2u9Z0/4XoZ8Sbcl71MpA== dependencies: "@babel/core" "^7.20.0" - hermes-parser "0.12.0" + hermes-parser "0.18.2" nullthrows "^1.1.1" -metro-cache-key@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.76.8.tgz#8a0a5e991c06f56fcc584acadacb313c312bdc16" - integrity sha512-buKQ5xentPig9G6T37Ww/R/bC+/V1MA5xU/D8zjnhlelsrPG6w6LtHUS61ID3zZcMZqYaELWk5UIadIdDsaaLw== +metro-cache-key@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.80.5.tgz#3fd0ce5a360e0455dc8b68a659c60abde3edac1d" + integrity sha512-fr3QLZUarsB3tRbVcmr34kCBsTHk0Sh9JXGvBY/w3b2lbre+Lq5gtgLyFElHPecGF7o4z1eK9r3ubxtScHWcbA== -metro-cache@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.76.8.tgz#296c1c189db2053b89735a8f33dbe82575f53661" - integrity sha512-QBJSJIVNH7Hc/Yo6br/U/qQDUpiUdRgZ2ZBJmvAbmAKp2XDzsapnMwK/3BGj8JNWJF7OLrqrYHsRsukSbUBpvQ== +metro-cache@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.80.5.tgz#adc2e48d87312d68f305f830b22359cb4362ce4b" + integrity sha512-2u+dQ4PZwmC7eZo9uMBNhQQMig9f+w4QWBZwXCdVy/RYOHM0eObgGdMEOwODo73uxie82T9lWzxr3aZOZ+Nqtw== dependencies: - metro-core "0.76.8" + metro-core "0.80.5" rimraf "^3.0.2" -metro-config@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.76.8.tgz#20bd5397fcc6096f98d2a813a7cecb38b8af062d" - integrity sha512-SL1lfKB0qGHALcAk2zBqVgQZpazDYvYFGwCK1ikz0S6Y/CM2i2/HwuZN31kpX6z3mqjv/6KvlzaKoTb1otuSAA== +metro-config@0.80.5, metro-config@^0.80.3: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.80.5.tgz#859e2ce307372f583bc8ebfe69bd233dd9328a8b" + integrity sha512-elqo/lwvF+VjZ1OPyvmW/9hSiGlmcqu+rQvDKw5F5WMX48ZC+ySTD1WcaD7e97pkgAlJHVYqZ98FCjRAYOAFRQ== dependencies: connect "^3.6.5" cosmiconfig "^5.0.5" - jest-validate "^29.2.1" - metro "0.76.8" - metro-cache "0.76.8" - metro-core "0.76.8" - metro-runtime "0.76.8" + jest-validate "^29.6.3" + metro "0.80.5" + metro-cache "0.80.5" + metro-core "0.80.5" + metro-runtime "0.80.5" -metro-core@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.76.8.tgz#917c8157c63406cb223522835abb8e7c6291dcad" - integrity sha512-sl2QLFI3d1b1XUUGxwzw/KbaXXU/bvFYrSKz6Sg19AdYGWFyzsgZ1VISRIDf+HWm4R/TJXluhWMEkEtZuqi3qA== +metro-core@0.80.5, metro-core@^0.80.3: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.80.5.tgz#3ead635dcecfa6d0b380f8a80d3e5406f70846a9" + integrity sha512-vkLuaBhnZxTVpaZO8ZJVEHzjaqSXpOdpAiztSZ+NDaYM6jEFgle3/XIbLW91jTSf2+T8Pj5yB1G7KuOX+BcVwg== dependencies: lodash.throttle "^4.1.1" - metro-resolver "0.76.8" + metro-resolver "0.80.5" -metro-file-map@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.76.8.tgz#a1db1185b6c316904ba6b53d628e5d1323991d79" - integrity sha512-A/xP1YNEVwO1SUV9/YYo6/Y1MmzhL4ZnVgcJC3VmHp/BYVOXVStzgVbWv2wILe56IIMkfXU+jpXrGKKYhFyHVw== +metro-file-map@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.80.5.tgz#1e2f0026c1a380a8802c977279018093c21b191e" + integrity sha512-bKCvJ05drjq6QhQxnDUt3I8x7bTcHo3IIKVobEr14BK++nmxFGn/BmFLRzVBlghM6an3gqwpNEYxS5qNc+VKcg== dependencies: anymatch "^3.0.3" debug "^2.2.0" fb-watchman "^2.0.0" graceful-fs "^4.2.4" invariant "^2.2.4" - jest-regex-util "^27.0.6" - jest-util "^27.2.0" - jest-worker "^27.2.0" + jest-worker "^29.6.3" micromatch "^4.0.4" node-abort-controller "^3.1.1" nullthrows "^1.1.1" @@ -7112,130 +7345,55 @@ metro-file-map@0.76.8: optionalDependencies: fsevents "^2.3.2" -metro-inspector-proxy@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.76.8.tgz#6b8678a7461b0b42f913a7881cc9319b4d3cddff" - integrity sha512-Us5o5UEd4Smgn1+TfHX4LvVPoWVo9VsVMn4Ldbk0g5CQx3Gu0ygc/ei2AKPGTwsOZmKxJeACj7yMH2kgxQP/iw== - dependencies: - connect "^3.6.5" - debug "^2.2.0" - node-fetch "^2.2.0" - ws "^7.5.1" - yargs "^17.6.2" - -metro-minify-terser@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.76.8.tgz#915ab4d1419257fc6a0b9fa15827b83fe69814bf" - integrity sha512-Orbvg18qXHCrSj1KbaeSDVYRy/gkro2PC7Fy2tDSH1c9RB4aH8tuMOIXnKJE+1SXxBtjWmQ5Yirwkth2DyyEZA== +metro-minify-terser@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.80.5.tgz#6163fc920faad46153456fcd191ccc1ce0450946" + integrity sha512-S7oZLLcab6YXUT6jYFX/ZDMN7Fq6xBGGAG8liMFU1UljX6cTcEC2u+UIafYgCLrdVexp/+ClxrIetVPZ5LtL/g== dependencies: terser "^5.15.0" -metro-minify-uglify@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.76.8.tgz#74745045ea2dd29f8783db483b2fce58385ba695" - integrity sha512-6l8/bEvtVaTSuhG1FqS0+Mc8lZ3Bl4RI8SeRIifVLC21eeSDp4CEBUWSGjpFyUDfi6R5dXzYaFnSgMNyfxADiQ== - dependencies: - uglify-es "^3.1.9" - -metro-react-native-babel-preset@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz#7476efae14363cbdfeeec403b4f01d7348e6c048" - integrity sha512-Ptza08GgqzxEdK8apYsjTx2S8WDUlS2ilBlu9DR1CUcHmg4g3kOkFylZroogVAUKtpYQNYwAvdsjmrSdDNtiAg== - dependencies: - "@babel/core" "^7.20.0" - "@babel/plugin-proposal-async-generator-functions" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.18.0" - "@babel/plugin-proposal-export-default-from" "^7.0.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" - "@babel/plugin-proposal-numeric-separator" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.20.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-default-from" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.18.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-syntax-optional-chaining" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.20.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.0.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.20.0" - "@babel/plugin-transform-flow-strip-types" "^7.20.0" - "@babel/plugin-transform-function-name" "^7.0.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" - "@babel/plugin-transform-parameters" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-react-jsx-self" "^7.0.0" - "@babel/plugin-transform-react-jsx-source" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-typescript" "^7.5.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - "@babel/template" "^7.0.0" - babel-plugin-transform-flow-enums "^0.0.2" - react-refresh "^0.4.0" - -metro-react-native-babel-transformer@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz#c3a98e1f4cd5faf1e21eba8e004b94a90c4db69b" - integrity sha512-3h+LfS1WG1PAzhq8QF0kfXjxuXetbY/lgz8vYMQhgrMMp17WM1DNJD0gjx8tOGYbpbBC1qesJ45KMS4o5TA73A== - dependencies: - "@babel/core" "^7.20.0" - babel-preset-fbjs "^3.4.0" - hermes-parser "0.12.0" - metro-react-native-babel-preset "0.76.8" - nullthrows "^1.1.1" - -metro-resolver@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.76.8.tgz#0862755b9b84e26853978322464fb37c6fdad76d" - integrity sha512-KccOqc10vrzS7ZhG2NSnL2dh3uVydarB7nOhjreQ7C4zyWuiW9XpLC4h47KtGQv3Rnv/NDLJYeDqaJ4/+140HQ== +metro-resolver@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.80.5.tgz#3915be3b2bcf4f3e9e2f24bdde8d8c9ac26bb134" + integrity sha512-haJ/Hveio3zv/Fr4eXVdKzjUeHHDogYok7OpRqPSXGhTXisNXB+sLN7CpcUrCddFRUDLnVaqQOYwhYsFndgUwA== -metro-runtime@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.76.8.tgz#74b2d301a2be5f3bbde91b8f1312106f8ffe50c3" - integrity sha512-XKahvB+iuYJSCr3QqCpROli4B4zASAYpkK+j3a0CJmokxCDNbgyI4Fp88uIL6rNaZfN0Mv35S0b99SdFXIfHjg== +metro-runtime@0.80.5, metro-runtime@^0.80.3: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.80.5.tgz#48ac4c732be195e0ebdefb5ac5d32c7da76305ad" + integrity sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg== dependencies: "@babel/runtime" "^7.0.0" - react-refresh "^0.4.0" -metro-source-map@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.76.8.tgz#f085800152a6ba0b41ca26833874d31ec36c5a53" - integrity sha512-Hh0ncPsHPVf6wXQSqJqB3K9Zbudht4aUtNpNXYXSxH+pteWqGAXnjtPsRAnCsCWl38wL0jYF0rJDdMajUI3BDw== +metro-source-map@0.80.5, metro-source-map@^0.80.3: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.80.5.tgz#21acdc1d5417cf3009209555d84cbeb61de5d6d5" + integrity sha512-DwSF4l03mKPNqCtyQ6K23I43qzU1BViAXnuH81eYWdHglP+sDlPpY+/7rUahXEo6qXEHXfAJgVoo1sirbXbmsQ== dependencies: "@babel/traverse" "^7.20.0" "@babel/types" "^7.20.0" invariant "^2.2.4" - metro-symbolicate "0.76.8" + metro-symbolicate "0.80.5" nullthrows "^1.1.1" - ob1 "0.76.8" + ob1 "0.80.5" source-map "^0.5.6" vlq "^1.0.0" -metro-symbolicate@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.76.8.tgz#f102ac1a306d51597ecc8fdf961c0a88bddbca03" - integrity sha512-LrRL3uy2VkzrIXVlxoPtqb40J6Bf1mlPNmUQewipc3qfKKFgtPHBackqDy1YL0njDsWopCKcfGtFYLn0PTUn3w== +metro-symbolicate@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.80.5.tgz#7c253a8e05e2a8380753373ab30705d6e81094a5" + integrity sha512-IsM4mTYvmo9JvIqwEkCZ5+YeDVPST78Q17ZgljfLdHLSpIivOHp9oVoiwQ/YGbLx0xRHRIS/tKiXueWBnj3UWA== dependencies: invariant "^2.2.4" - metro-source-map "0.76.8" + metro-source-map "0.80.5" nullthrows "^1.1.1" source-map "^0.5.6" through2 "^2.0.1" vlq "^1.0.0" -metro-transform-plugins@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.76.8.tgz#d77c28a6547a8e3b72250f740fcfbd7f5408f8ba" - integrity sha512-PlkGTQNqS51Bx4vuufSQCdSn2R2rt7korzngo+b5GCkeX5pjinPjnO2kNhQ8l+5bO0iUD/WZ9nsM2PGGKIkWFA== +metro-transform-plugins@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.80.5.tgz#ef90d8d6522b042b832a571381f1a5a9c2c72adf" + integrity sha512-7IdlTqK/k5+qE3RvIU5QdCJUPk4tHWEqgVuYZu8exeW+s6qOJ66hGIJjXY/P7ccucqF+D4nsbAAW5unkoUdS6g== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.0" @@ -7243,28 +7401,28 @@ metro-transform-plugins@0.76.8: "@babel/traverse" "^7.20.0" nullthrows "^1.1.1" -metro-transform-worker@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.76.8.tgz#b9012a196cee205170d0c899b8b175b9305acdea" - integrity sha512-mE1fxVAnJKmwwJyDtThildxxos9+DGs9+vTrx2ktSFMEVTtXS/bIv2W6hux1pqivqAfyJpTeACXHk5u2DgGvIQ== +metro-transform-worker@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.80.5.tgz#45b78093f5925cbbf1ecf2695114469dd49f5169" + integrity sha512-Q1oM7hfP+RBgAtzRFBDjPhArELUJF8iRCZ8OidqCpYzQJVGuJZ7InSnIf3hn1JyqiUQwv2f1LXBO78i2rAjzyA== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.0" "@babel/parser" "^7.20.0" "@babel/types" "^7.20.0" - babel-preset-fbjs "^3.4.0" - metro "0.76.8" - metro-babel-transformer "0.76.8" - metro-cache "0.76.8" - metro-cache-key "0.76.8" - metro-source-map "0.76.8" - metro-transform-plugins "0.76.8" + metro "0.80.5" + metro-babel-transformer "0.80.5" + metro-cache "0.80.5" + metro-cache-key "0.80.5" + metro-minify-terser "0.80.5" + metro-source-map "0.80.5" + metro-transform-plugins "0.80.5" nullthrows "^1.1.1" -metro@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/metro/-/metro-0.76.8.tgz#ba526808b99977ca3f9ac5a7432fd02a340d13a6" - integrity sha512-oQA3gLzrrYv3qKtuWArMgHPbHu8odZOD9AoavrqSFllkPgOtmkBvNNDLCELqv5SjBfqjISNffypg+5UGG3y0pg== +metro@0.80.5, metro@^0.80.3: + version "0.80.5" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.80.5.tgz#94cd36df2eab434f050314a6d31529e8dc1b26df" + integrity sha512-OE/CGbOgbi8BlTN1QqJgKOBaC27dS0JBQw473JcivrpgVnqIsluROA7AavEaTVUrB9wPUZvoNVDROn5uiM2jfw== dependencies: "@babel/code-frame" "^7.0.0" "@babel/core" "^7.20.0" @@ -7274,7 +7432,6 @@ metro@0.76.8: "@babel/traverse" "^7.20.0" "@babel/types" "^7.20.0" accepts "^1.3.7" - async "^3.2.2" chalk "^4.0.0" ci-info "^2.0.0" connect "^3.6.5" @@ -7282,28 +7439,24 @@ metro@0.76.8: denodeify "^1.2.1" error-stack-parser "^2.0.6" graceful-fs "^4.2.4" - hermes-parser "0.12.0" + hermes-parser "0.18.2" image-size "^1.0.2" invariant "^2.2.4" - jest-worker "^27.2.0" + jest-worker "^29.6.3" jsc-safe-url "^0.2.2" lodash.throttle "^4.1.1" - metro-babel-transformer "0.76.8" - metro-cache "0.76.8" - metro-cache-key "0.76.8" - metro-config "0.76.8" - metro-core "0.76.8" - metro-file-map "0.76.8" - metro-inspector-proxy "0.76.8" - metro-minify-terser "0.76.8" - metro-minify-uglify "0.76.8" - metro-react-native-babel-preset "0.76.8" - metro-resolver "0.76.8" - metro-runtime "0.76.8" - metro-source-map "0.76.8" - metro-symbolicate "0.76.8" - metro-transform-plugins "0.76.8" - metro-transform-worker "0.76.8" + metro-babel-transformer "0.80.5" + metro-cache "0.80.5" + metro-cache-key "0.80.5" + metro-config "0.80.5" + metro-core "0.80.5" + metro-file-map "0.80.5" + metro-resolver "0.80.5" + metro-runtime "0.80.5" + metro-source-map "0.80.5" + metro-symbolicate "0.80.5" + metro-transform-plugins "0.80.5" + metro-transform-worker "0.80.5" mime-types "^2.1.27" node-fetch "^2.2.0" nullthrows "^1.1.1" @@ -7328,7 +7481,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -7362,13 +7515,6 @@ mimic-fn@^2.1.0: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^9.0.1: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -7402,14 +7548,7 @@ minipass-pipeline@^1.2.2: dependencies: minipass "^3.0.0" -minipass@3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" - integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== - dependencies: - yallist "^4.0.0" - -minipass@^3.0.0, minipass@^3.1.1: +minipass@3.3.6, minipass@^3.0.0, minipass@^3.1.1: version "3.3.6" resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== @@ -7446,6 +7585,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -7635,10 +7779,10 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== -ob1@0.76.8: - version "0.76.8" - resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.76.8.tgz#ac4c459465b1c0e2c29aaa527e09fc463d3ffec8" - integrity sha512-dlBkJJV5M/msj9KYA9upc+nUWVwuOFFTbu28X6kZeGwcuW+JxaHSBZ70SYQnk5M+j5JbNLR6yKHmgW4M5E7X5g== +ob1@0.80.5: + version "0.80.5" + resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.80.5.tgz#101f5257f7e6b75599dcd55c20bfcf2a4016c37c" + integrity sha512-zYDMnnNrFi/1Tqh0vo3PE4p97Tpl9/4MP2k2ECvkbLOZzQuAYZJLTUYVLZb7hJhbhjT+JJxAwBGS8iu5hCSd1w== object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -7752,6 +7896,14 @@ open@^6.2.0: dependencies: is-wsl "^1.1.0" +open@^7.0.3: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + open@^8.0.4, open@^8.3.0: version "8.4.2" resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" @@ -7773,7 +7925,7 @@ optionator@^0.9.3: prelude-ls "^1.2.1" type-check "^0.4.0" -ora@3.4.0: +ora@3.4.0, ora@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== @@ -7922,11 +8074,6 @@ password-prompt@^1.0.4: ansi-escapes "^4.3.2" cross-spawn "^7.0.3" -path-browserify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" - integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -7980,6 +8127,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" + integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8009,13 +8161,6 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-up@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - plist@^3.0.5: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" @@ -8105,7 +8250,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.12, postcss@^8.4.23, postcss@^8.4.33, postcss@~8.4.21: +postcss@^8.4.12, postcss@^8.4.23, postcss@^8.4.33, postcss@~8.4.32: version "8.4.33" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== @@ -8131,10 +8276,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.1.tgz#babf33580e16c796a9740b9fae551624f7bfeaab" - integrity sha512-qSUWshj1IobVbKc226Gw2pync27t0Kf0EdufZa9j7uBSJay1CC+B3K5lAAZoqgX3ASiKuWsk6OmzKRetXNObWg== +prettier@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.4.tgz#4723cadeac2ce7c9227de758e5ff9b14e075f283" + integrity sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ== pretty-bytes@5.6.0: version "5.6.0" @@ -8189,7 +8334,7 @@ promise@^8.3.0: dependencies: asap "~2.0.6" -prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0: +prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -8197,7 +8342,7 @@ prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@*, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -8251,23 +8396,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.10.3, qs@^6.11.0, qs@^6.11.2: +qs@^6.11.2: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== dependencies: side-channel "^1.0.4" -query-string@7.1.3, query-string@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" - integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== - dependencies: - decode-uri-component "^0.2.2" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -8277,6 +8412,16 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +query-string@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" @@ -8299,16 +8444,6 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - rc@~1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -8319,7 +8454,7 @@ rc@~1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-devtools-core@^4.27.2: +react-devtools-core@^4.27.7: version "4.28.5" resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.28.5.tgz#c8442b91f068cdf0c899c543907f7f27d79c2508" integrity sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA== @@ -8371,10 +8506,10 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-native-gesture-handler@~2.12.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz#f11a99fb95169810c6886fad5efa01a17fd81660" - integrity sha512-deqh36bw82CFUV9EC4tTo2PP1i9HfCOORGS3Zmv71UYhEZEHkzZv18IZNPB+2Awzj45vLIidZxGYGFxHlDSQ5A== +react-native-gesture-handler@~2.14.1: + version "2.14.1" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz#930640231024b7921435ab476aa501dd4a6b2e01" + integrity sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" @@ -8382,15 +8517,15 @@ react-native-gesture-handler@~2.12.0: lodash "^4.17.21" prop-types "^15.7.2" -react-native-safe-area-context@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.6.3.tgz#f06cfea05b1c4b018aa9758667a109f619c62b55" - integrity sha512-3CeZM9HFXkuqiU9HqhOQp1yxhXw6q99axPWrT+VJkITd67gnPSU03+U27Xk2/cr9XrLUnakM07kj7H0hdPnFiQ== +react-native-safe-area-context@4.8.2: + version "4.8.2" + resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.8.2.tgz#e6b3d8acf3c6afcb4b5db03a97f9c37df7668f65" + integrity sha512-ffUOv8BJQ6RqO3nLml5gxJ6ab3EestPiyWekxdzO/1MQ7NF8fW1Mzh1C5QE9yq573Xefnc7FuzGXjtesZGv7cQ== -react-native-screens@~3.22.0: - version "3.22.1" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.22.1.tgz#b0eb0696dbf1f9a852061cc71c0f8cdb95ed8e53" - integrity sha512-ffzwUdVKf+iLqhWSzN5DXBm0s2w5sN0P+TaHHPAx42LT7+DT0g8PkHT1QDvxpR5vCEPSS1i3EswyVK4HCuhTYg== +react-native-screens@~3.29.0: + version "3.29.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.29.0.tgz#1dee0326defbc1d4ef4e68287abb32a8e6b76b29" + integrity sha512-yB1GoAMamFAcYf4ku94uBPn0/ani9QG7NdI98beJ5cet2YFESYYzuEIuU+kt+CNRcO8qqKeugxlfgAa3HyTqlg== dependencies: react-freeze "^1.0.0" warn-once "^0.1.0" @@ -8416,49 +8551,51 @@ react-native-web@~0.19.6: postcss-value-parser "^4.2.0" styleq "^0.1.3" -react-native@0.72.6: - version "0.72.6" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.72.6.tgz#9f8d090694907e2f83af22e115cc0e4a3d5fa626" - integrity sha512-RafPY2gM7mcrFySS8TL8x+TIO3q7oAlHpzEmC7Im6pmXni6n1AuufGaVh0Narbr1daxstw7yW7T9BKW5dpVc2A== - dependencies: - "@jest/create-cache-key-function" "^29.2.1" - "@react-native-community/cli" "11.3.7" - "@react-native-community/cli-platform-android" "11.3.7" - "@react-native-community/cli-platform-ios" "11.3.7" - "@react-native/assets-registry" "^0.72.0" - "@react-native/codegen" "^0.72.7" - "@react-native/gradle-plugin" "^0.72.11" - "@react-native/js-polyfills" "^0.72.1" - "@react-native/normalize-colors" "^0.72.0" - "@react-native/virtualized-lists" "^0.72.8" +react-native@0.73.3: + version "0.73.3" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.3.tgz#aae18b4c6da84294c1f8e1d6446b46c887bf087c" + integrity sha512-RSQDtT2DNUcmB4IgmW9NhRb5wqvXFl6DI2NEJmt0ps2OrVHpoA8Tkq+lkFOA/fvPscJKtFKEHFBDSR5UHR3PUw== + dependencies: + "@jest/create-cache-key-function" "^29.6.3" + "@react-native-community/cli" "12.3.2" + "@react-native-community/cli-platform-android" "12.3.2" + "@react-native-community/cli-platform-ios" "12.3.2" + "@react-native/assets-registry" "0.73.1" + "@react-native/codegen" "0.73.2" + "@react-native/community-cli-plugin" "0.73.14" + "@react-native/gradle-plugin" "0.73.4" + "@react-native/js-polyfills" "0.73.1" + "@react-native/normalize-colors" "0.73.2" + "@react-native/virtualized-lists" "0.73.4" abort-controller "^3.0.0" anser "^1.4.9" - base64-js "^1.1.2" - deprecated-react-native-prop-types "4.1.0" + ansi-regex "^5.0.0" + base64-js "^1.5.1" + chalk "^4.0.0" + deprecated-react-native-prop-types "^5.0.0" event-target-shim "^5.0.1" - flow-enums-runtime "^0.0.5" + flow-enums-runtime "^0.0.6" invariant "^2.2.4" - jest-environment-node "^29.2.1" + jest-environment-node "^29.6.3" jsc-android "^250231.0.0" memoize-one "^5.0.0" - metro-runtime "0.76.8" - metro-source-map "0.76.8" + metro-runtime "^0.80.3" + metro-source-map "^0.80.3" mkdirp "^0.5.1" nullthrows "^1.1.1" pretty-format "^26.5.2" promise "^8.3.0" - react-devtools-core "^4.27.2" - react-refresh "^0.4.0" + react-devtools-core "^4.27.7" + react-refresh "^0.14.0" react-shallow-renderer "^16.15.0" regenerator-runtime "^0.13.2" scheduler "0.24.0-canary-efb381bbf-20230505" stacktrace-parser "^0.1.10" - use-sync-external-store "^1.0.0" whatwg-fetch "^3.0.0" ws "^6.2.2" yargs "^17.6.2" -react-refresh@^0.4.0, react-refresh@~0.14.0: +react-refresh@0.14.0, react-refresh@^0.14.0, react-refresh@~0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== @@ -8641,11 +8778,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -reselect@^4.1.7: - version "4.1.8" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" - integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -8668,12 +8800,12 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve.exports@^2.0.0: +resolve.exports@^2.0.0, resolve.exports@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.2: +resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.2: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -8788,7 +8920,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.2.2" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -8841,6 +8973,13 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" +semver@7.6.0, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + semver@^5.5.0, semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -8851,13 +8990,6 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - send@0.18.0, send@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -8877,13 +9009,6 @@ send@0.18.0, send@^0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-error@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-6.0.0.tgz#ccfb887a1dd1c48d6d52d7863b92544331fd752b" - integrity sha512-3vmBkMZLQO+BR4RPHcyRGdE09XCF6cvxzk2N2qn8Er3F91cy8Qt7VvEbZBOpaL53qsBbe2cFOefU6tRY6WDelA== - dependencies: - type-fest "^0.12.0" - serialize-error@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" @@ -8904,6 +9029,11 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-cookie-parser@^2.4.8: + version "2.6.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" + integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== + set-function-length@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" @@ -9033,7 +9163,7 @@ slice-ansi@^2.0.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -slugify@^1.3.4: +slugify@^1.3.4, slugify@^1.6.6: version "1.6.6" resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== @@ -9058,7 +9188,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@^0.5.16, source-map-support@~0.5.20: +source-map-support@^0.5.16, source-map-support@^0.5.21, source-map-support@~0.5.20, source-map-support@~0.5.21: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -9066,6 +9196,11 @@ source-map-support@^0.5.16, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== + source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -9105,6 +9240,13 @@ ssri@^8.0.1: dependencies: minipass "^3.1.1" +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -9117,6 +9259,23 @@ stackframe@^1.3.4: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + stacktrace-parser@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" @@ -9139,6 +9298,11 @@ stream-buffers@2.2.x: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== +stream-slice@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/stream-slice/-/stream-slice-0.1.2.tgz#2dc4f4e1b936fb13f3eb39a2def1932798d07a4b" + integrity sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -9171,6 +9335,7 @@ string-natural-compare@^3.0.1: integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9245,6 +9410,7 @@ string_decoder@~1.1.1: safe-buffer "~5.1.0" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9310,6 +9476,19 @@ stylis@4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +sucrase@3.34.0: + version "3.34.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" + integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + sucrase@^3.20.0, sucrase@^3.32.0: version "3.35.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" @@ -9397,20 +9576,20 @@ tabbable@^6.0.1: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== -tailwindcss@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3" - integrity sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w== +tailwindcss@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" + integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" chokidar "^3.5.3" didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.2.12" + fast-glob "^3.3.0" glob-parent "^6.0.2" is-glob "^4.0.3" - jiti "^1.18.2" + jiti "^1.19.1" lilconfig "^2.1.0" micromatch "^4.0.5" normalize-path "^3.0.0" @@ -9422,7 +9601,6 @@ tailwindcss@3.3.2: postcss-load-config "^4.0.1" postcss-nested "^6.0.1" postcss-selector-parser "^6.0.11" - postcss-value-parser "^4.2.0" resolve "^1.22.2" sucrase "^3.32.0" @@ -9639,11 +9817,6 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.12.0.tgz#f57a27ab81c68d136a51fd71467eff94157fa1ee" - integrity sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg== - type-fest@^0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" @@ -9674,14 +9847,6 @@ type-fest@^3.0.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706" integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typed-array-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" @@ -9731,14 +9896,6 @@ ua-parser-js@^1.0.35: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== -uglify-es@^3.1.9: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -9825,7 +9982,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== @@ -9850,7 +10007,7 @@ url-join@4.0.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a" integrity sha512-EGXjXJZhIHiQMK2pQukuFcL303nskqIRzWvPvV5O8miOfwoUb9G+a/Cld60kUyeaybEI94wvVClT10DtfeAExA== -url-parse@^1.5.3, url-parse@^1.5.9: +url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== @@ -9871,7 +10028,7 @@ use-latest-callback@^0.1.7: resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.9.tgz#10191dc54257e65a8e52322127643a8940271e2a" integrity sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw== -use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.1.0, use-sync-external-store@^1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.1.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== @@ -9881,16 +10038,22 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.12.3: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2, uuid@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" @@ -9958,6 +10121,20 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-encoding@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" + integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== + dependencies: + util "^0.12.3" + optionalDependencies: + "@zxing/text-encoding" "0.9.0" + +web-streams-polyfill@^3.1.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz#32e26522e05128203a7de59519be3c648004343b" + integrity sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -10059,7 +10236,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: +which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.13" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== @@ -10095,6 +10272,7 @@ wonka@^6.3.2: integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -10289,9 +10467,9 @@ zod@^3.22.4: resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== -zustand@^4.4.7: - version "4.4.7" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c" - integrity sha512-QFJWJMdlETcI69paJwhSMJz7PPWjVP8Sjhclxmxmxv/RYI7ZOvR5BHX+ktH0we9gTWQMxcne8q1OY8xxz604gw== +zustand@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0" + integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A== dependencies: use-sync-external-store "1.2.0" diff --git a/frontend/sac-web/package-lock.json b/frontend/sac-web/package-lock.json index 819f9f966..528b4389c 100644 --- a/frontend/sac-web/package-lock.json +++ b/frontend/sac-web/package-lock.json @@ -8,17 +8,18 @@ "name": "sac-web", "version": "0.1.0", "dependencies": { - "next": "14.0.4", + "next": "14.1.0", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "semver": "7.5.4" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", + "autoprefixer": "^10.4.17", "eslint": "^8", - "eslint-config-next": "14.0.4", + "eslint-config-next": "14.1.0", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" @@ -239,23 +240,69 @@ } }, "node_modules/@next/env": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", - "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==" + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", + "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.4.tgz", - "integrity": "sha512-U3qMNHmEZoVmHA0j/57nRfi3AscXNvkOnxDmle/69Jz/G0o/gWjXTDdlgILZdrxQ0Lw/jv2mPW8PGy0EGIHXhQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.0.tgz", + "integrity": "sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q==", "dev": true, "dependencies": { - "glob": "7.1.7" + "glob": "10.3.10" + } + }, + "node_modules/@next/eslint-plugin-next/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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", - "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", + "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", "cpu": [ "arm64" ], @@ -268,9 +315,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", - "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", + "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", "cpu": [ "x64" ], @@ -283,9 +330,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", - "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", + "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", "cpu": [ "arm64" ], @@ -298,9 +345,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", - "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", + "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", "cpu": [ "arm64" ], @@ -313,9 +360,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", - "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", + "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", "cpu": [ "x64" ], @@ -328,9 +375,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", - "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", + "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", "cpu": [ "x64" ], @@ -343,9 +390,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", - "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", + "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", "cpu": [ "arm64" ], @@ -358,9 +405,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", - "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", + "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", "cpu": [ "ia32" ], @@ -373,9 +420,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", - "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", + "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", "cpu": [ "x64" ], @@ -453,9 +500,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.2.tgz", - "integrity": "sha512-cZShBaVa+UO1LjWWBPmWRR4+/eY/JR/UIEcDlVsw3okjWEu+rB7/mH6X3B/L+qJVHDLjk9QW/y2upp9wp1yDXA==", + "version": "20.11.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", + "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -468,9 +515,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.48", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", - "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "version": "18.2.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.53.tgz", + "integrity": "sha512-52IHsMDT8qATp9B9zoOyobW8W3/0QhaJQTw1HwRj0UY2yBpCAQ7+S/CqHYQ8niAm3p4ji+rWUQ9UCib0GxQ60w==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -563,7 +610,7 @@ "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", - "semver": "^7.5.4", + "semver": "7.5.4", "ts-api-utils": "^1.0.1" }, "engines": { @@ -873,9 +920,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "funding": [ { @@ -892,9 +939,9 @@ } ], "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -1052,9 +1099,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001576", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", - "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "version": "1.0.30001580", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", + "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", "funding": [ { "type": "opencollective", @@ -1523,12 +1570,12 @@ } }, "node_modules/eslint-config-next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.4.tgz", - "integrity": "sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.1.0.tgz", + "integrity": "sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.0.4", + "@next/eslint-plugin-next": "14.1.0", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", @@ -2161,11 +2208,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -2924,7 +2966,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3025,18 +3066,17 @@ "dev": true }, "node_modules/next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", - "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", + "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", "dependencies": { - "@next/env": "14.0.4", + "@next/env": "14.1.0", "@swc/helpers": "0.5.2", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" @@ -3045,15 +3085,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.4", - "@next/swc-darwin-x64": "14.0.4", - "@next/swc-linux-arm64-gnu": "14.0.4", - "@next/swc-linux-arm64-musl": "14.0.4", - "@next/swc-linux-x64-gnu": "14.0.4", - "@next/swc-linux-x64-musl": "14.0.4", - "@next/swc-win32-arm64-msvc": "14.0.4", - "@next/swc-win32-ia32-msvc": "14.0.4", - "@next/swc-win32-x64-msvc": "14.0.4" + "@next/swc-darwin-arm64": "14.1.0", + "@next/swc-darwin-x64": "14.1.0", + "@next/swc-linux-arm64-gnu": "14.1.0", + "@next/swc-linux-arm64-musl": "14.1.0", + "@next/swc-linux-x64-gnu": "14.1.0", + "@next/swc-linux-x64-musl": "14.1.0", + "@next/swc-win32-arm64-msvc": "14.1.0", + "@next/swc-win32-ia32-msvc": "14.1.0", + "@next/swc-win32-x64-msvc": "14.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -3836,7 +3876,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -4527,18 +4566,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4733,8 +4760,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.3.4", diff --git a/frontend/sac-web/package.json b/frontend/sac-web/package.json index 10e7065fd..e3b09058a 100644 --- a/frontend/sac-web/package.json +++ b/frontend/sac-web/package.json @@ -9,19 +9,20 @@ "lint": "next lint" }, "dependencies": { + "next": "14.1.0", "react": "^18", "react-dom": "^18", - "next": "14.0.4" + "semver": "7.5.4" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "autoprefixer": "^10.0.1", + "autoprefixer": "^10.4.17", + "eslint": "^8", + "eslint-config-next": "14.1.0", "postcss": "^8", "tailwindcss": "^3.3.0", - "eslint": "^8", - "eslint-config-next": "14.0.4" + "typescript": "^5" } } diff --git a/frontend/sac-web/yarn.lock b/frontend/sac-web/yarn.lock new file mode 100644 index 000000000..c9cebbb1a --- /dev/null +++ b/frontend/sac-web/yarn.lock @@ -0,0 +1,2629 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@babel/runtime@^7.23.2": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + 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" + +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== + +"@humanwhocodes/config-array@^0.11.13": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + 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" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.22" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@next/env@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.0.tgz#43d92ebb53bc0ae43dcc64fb4d418f8f17d7a341" + integrity sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw== + +"@next/eslint-plugin-next@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.0.tgz#29b041233fac7417e22eefa4146432d5cd910820" + integrity sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q== + dependencies: + glob "10.3.10" + +"@next/swc-darwin-arm64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz#70a57c87ab1ae5aa963a3ba0f4e59e18f4ecea39" + integrity sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ== + +"@next/swc-darwin-x64@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz#0863a22feae1540e83c249384b539069fef054e9" + integrity sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g== + +"@next/swc-linux-arm64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz#893da533d3fce4aec7116fe772d4f9b95232423c" + integrity sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ== + +"@next/swc-linux-arm64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz#d81ddcf95916310b8b0e4ad32b637406564244c0" + integrity sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g== + +"@next/swc-linux-x64-gnu@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz#18967f100ec19938354332dcb0268393cbacf581" + integrity sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ== + +"@next/swc-linux-x64-musl@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz#77077cd4ba8dda8f349dc7ceb6230e68ee3293cf" + integrity sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg== + +"@next/swc-win32-arm64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz#5f0b8cf955644104621e6d7cc923cad3a4c5365a" + integrity sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ== + +"@next/swc-win32-ia32-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz#21f4de1293ac5e5a168a412b139db5d3420a89d0" + integrity sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw== + +"@next/swc-win32-x64-msvc@14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz#e561fb330466d41807123d932b365cf3d33ceba2" + integrity sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@rushstack/eslint-patch@^1.3.3": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz#2d4260033e199b3032a08b41348ac10de21c47e9" + integrity sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA== + +"@swc/helpers@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d" + integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw== + dependencies: + tslib "^2.4.0" + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/node@^20": + version "20.11.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" + integrity sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ== + dependencies: + undici-types "~5.26.4" + +"@types/prop-types@*": + version "15.7.11" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + +"@types/react-dom@^18": + version "18.2.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" + integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18": + version "18.2.53" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.53.tgz#09c21b4621aaad5fed6a5045b33a7430749d8dc5" + integrity sha512-52IHsMDT8qATp9B9zoOyobW8W3/0QhaJQTw1HwRj0UY2yBpCAQ7+S/CqHYQ8niAm3p4ji+rWUQ9UCib0GxQ60w== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== + +"@typescript-eslint/parser@^5.4.2 || ^6.0.0": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.19.1.tgz#68a87bb21afaf0b1689e9cdce0e6e75bc91ada78" + integrity sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ== + dependencies: + "@typescript-eslint/scope-manager" "6.19.1" + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/typescript-estree" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz#2f527ee30703a6169a52b31d42a1103d80acd51b" + integrity sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w== + dependencies: + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" + +"@typescript-eslint/types@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.19.1.tgz#2d4c9d492a63ede15e7ba7d129bdf7714b77f771" + integrity sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg== + +"@typescript-eslint/typescript-estree@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz#796d88d88882f12e85bb33d6d82d39e1aea54ed1" + integrity sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA== + dependencies: + "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/visitor-keys" "6.19.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/visitor-keys@6.19.1": + version "6.19.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz#2164073ed4fc34a5ff3b5e25bb5a442100454c4c" + integrity sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ== + dependencies: + "@typescript-eslint/types" "6.19.1" + eslint-visitor-keys "^3.4.1" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + 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" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-includes@^3.1.6, array-includes@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" + integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-string "^1.0.7" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" + integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" + integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.tosorted@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" + integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + +asynciterator.prototype@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" + integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== + dependencies: + has-symbols "^1.0.3" + +autoprefixer@^10.4.17: + version "10.4.17" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.17.tgz#35cd5695cbbe82f536a50fa025d561b01fdec8be" + integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg== + dependencies: + browserslist "^4.22.2" + caniuse-lite "^1.0.30001578" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +axe-core@=4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" + integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== + +axobject-query@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" + integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== + dependencies: + dequal "^2.0.3" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.22.2: + version "4.22.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" + integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== + dependencies: + caniuse-lite "^1.0.30001580" + electron-to-chromium "^1.4.648" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +busboy@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" + integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== + dependencies: + function-bind "^1.1.2" + get-intrinsic "^1.2.1" + set-function-length "^1.1.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30001578, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001580: + version "1.0.30001580" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz#e3c76bc6fe020d9007647044278954ff8cd17d1e" + integrity sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-data-property@^1.0.1, define-data-property@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" + integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.4.648: + version "1.4.648" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz#c7b46c9010752c37bb4322739d6d2dd82354fbe4" + integrity sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enhanced-resolve@^5.12.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.5" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.2" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.13" + +es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" + integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== + dependencies: + asynciterator.prototype "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.1" + es-abstract "^1.22.1" + es-set-tostringtag "^2.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + iterator.prototype "^1.1.2" + safe-array-concat "^1.0.1" + +es-set-tostringtag@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" + integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== + dependencies: + get-intrinsic "^1.2.2" + has-tostringtag "^1.0.0" + hasown "^2.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" + integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== + dependencies: + hasown "^2.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-next@14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-14.1.0.tgz#7e309d426b8afacaba3b32fdbb02ba220b6d0a97" + integrity sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg== + dependencies: + "@next/eslint-plugin-next" "14.1.0" + "@rushstack/eslint-patch" "^1.3.3" + "@typescript-eslint/parser" "^5.4.2 || ^6.0.0" + eslint-import-resolver-node "^0.3.6" + eslint-import-resolver-typescript "^3.5.2" + eslint-plugin-import "^2.28.1" + eslint-plugin-jsx-a11y "^6.7.1" + eslint-plugin-react "^7.33.2" + eslint-plugin-react-hooks "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + +eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.5.2: + version "3.6.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" + integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" + is-glob "^4.0.3" + +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.28.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-plugin-jsx-a11y@^6.7.1: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" + integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + dependencies: + "@babel/runtime" "^7.23.2" + aria-query "^5.3.0" + array-includes "^3.1.7" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "=4.7.0" + axobject-query "^3.2.1" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + es-iterator-helpers "^1.0.15" + hasown "^2.0.0" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" + +"eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react@^7.33.2: + version "7.33.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" + integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== + dependencies: + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + array.prototype.tosorted "^1.1.1" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.12" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + object.hasown "^1.1.2" + object.values "^1.1.6" + prop-types "^15.8.1" + resolve "^2.0.0-next.4" + semver "^6.3.1" + string.prototype.matchall "^4.0.8" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8: + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" + "@humanwhocodes/config-array" "^0.11.13" + "@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" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.0.tgz#ca5e1a90b5e68f97fc8b61330d5819b82f5fab03" + integrity sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1, function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" + integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== + dependencies: + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-tsconfig@^4.5.0: + version "4.7.2" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" + integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + dependencies: + resolve-pkg-maps "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@10.3.10, glob@^10.3.10: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + 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" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + 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" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.2.11, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== + dependencies: + get-intrinsic "^1.2.2" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +ignore@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" + integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== + dependencies: + get-intrinsic "^1.2.2" + hasown "^2.0.0" + side-channel "^1.0.4" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.19.1: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +language-subtag-registry@^0.3.20: + version "0.3.22" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lilconfig@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" + integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +"lru-cache@^9.1.1 || ^10.0.0": + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimatch@9.0.3, minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.6, nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +next@14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/next/-/next-14.1.0.tgz#b31c0261ff9caa6b4a17c5af019ed77387174b69" + integrity sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q== + dependencies: + "@next/env" "14.1.0" + "@swc/helpers" "0.5.2" + busboy "1.6.0" + caniuse-lite "^1.0.30001579" + graceful-fs "^4.2.11" + postcss "8.4.31" + styled-jsx "5.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "14.1.0" + "@next/swc-darwin-x64" "14.1.0" + "@next/swc-linux-arm64-gnu" "14.1.0" + "@next/swc-linux-arm64-musl" "14.1.0" + "@next/swc-linux-x64-gnu" "14.1.0" + "@next/swc-linux-x64-musl" "14.1.0" + "@next/swc-win32-arm64-msvc" "14.1.0" + "@next/swc-win32-ia32-msvc" "14.1.0" + "@next/swc-win32-x64-msvc" "14.1.0" + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +object-inspect@^1.13.1, object-inspect@^1.9.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.entries@^1.1.6, object.entries@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" + integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.fromentries@^2.0.6, object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + +object.hasown@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" + integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== + dependencies: + define-properties "^1.2.0" + es-abstract "^1.22.1" + +object.values@^1.1.6, object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-selector-parser@^6.0.11: + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8, postcss@^8.4.23: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-dom@^18: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react@^18: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +reflect.getprototypeof@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" + integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.1.7, resolve@^1.22.2, resolve@^1.22.4: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.4: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-array-concat@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.0.tgz#8d0cae9cb806d6d1c06e08ab13d847293ebe0692" + integrity sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg== + dependencies: + call-bind "^1.0.5" + get-intrinsic "^1.2.2" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-regex-test@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.2.tgz#3ba32bdb3ea35f940ee87e5087c60ee786c3f6c5" + integrity sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ== + dependencies: + call-bind "^1.0.5" + get-intrinsic "^1.2.2" + is-regex "^1.1.4" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +semver@7.5.4, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +set-function-length@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.0.tgz#2f81dc6c16c7059bda5ab7c82c11f03a515ed8e1" + integrity sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w== + dependencies: + define-data-property "^1.1.1" + function-bind "^1.1.2" + get-intrinsic "^1.2.2" + gopd "^1.0.1" + has-property-descriptors "^1.0.1" + +set-function-name@^2.0.0, set-function-name@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.matchall@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" + integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" + set-function-name "^2.0.0" + side-channel "^1.0.4" + +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +styled-jsx@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f" + integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== + dependencies: + client-only "0.0.1" + +sucrase@^3.32.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwindcss@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" + integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.19.1" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +typescript@^5: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + +which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: + version "1.1.13" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" + integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.4" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/frontend/yarn.lock b/frontend/yarn.lock deleted file mode 100644 index fb57ccd13..000000000 --- a/frontend/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/go.work b/go.work index 5a962e04b..ca90035f7 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,6 @@ -go 1.21.1 +go 1.22.0 use ( ./backend ./cli -) +) \ No newline at end of file diff --git a/go.work.sum b/go.work.sum index 71c01eeaa..e1e1a449a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,35 +1,39 @@ +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/mcnijman/go-emailaddress v1.1.1 h1:AGhgVDG3tCDaL0/Vc6erlPQjDuDN3dAT7rRdgFtetr0= -github.com/mcnijman/go-emailaddress v1.1.1/go.mod h1:5whZrhS8Xp5LxO8zOD35BC+b76kROtsh+dPomeRt/II= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= @@ -42,28 +46,62 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4= google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= +gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 000000000..25e1c2b25 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,27 @@ +# Get the absolute path to the current script +$ScriptPath = (Get-Item -Path $MyInvocation.MyCommand.Path).DirectoryName + +# Build the Go CLI tool +go build -o "sac-cli" "cli/main.go" + +# Check if sac-cli is already installed +if (Test-Path -Path "$Env:USERPROFILE\AppData\Local\Programs\sac-cli\sac-cli.exe") { + Write-Host "sac-cli is already installed." + exit 1 +} + +# Copy the sac-cli executable to a directory in the user's PATH +$InstallPath = "$Env:USERPROFILE\AppData\Local\Programs\sac-cli" +if (-not (Test-Path -Path $InstallPath)) { + New-Item -ItemType Directory -Path $InstallPath | Out-Null +} +Copy-Item -Path "sac-cli" -Destination "$InstallPath\sac-cli.exe" -Force + +# Add the installation path to the user's PATH +$PathEnvVar = [System.Environment]::GetEnvironmentVariable("PATH", [System.EnvironmentVariableTarget]::User) +if (-not ($PathEnvVar -like "*$InstallPath*")) { + [System.Environment]::SetEnvironmentVariable("PATH", "$InstallPath;$PathEnvVar", [System.EnvironmentVariableTarget]::User) +} + +# Inform the user +Write-Host "Installation complete. You can now run 'sac-cli' from anywhere." diff --git a/install.sh b/install.sh index 9a5b8e7bf..990c9456e 100755 --- a/install.sh +++ b/install.sh @@ -1,11 +1,10 @@ #!/bin/zsh + set -e set -o pipefail # Get the absolute path to the current script -SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# /Users/davidoduneye/northeastern/org-generate/sac +SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Build the Go CLI tool go build -o "sac-cli" "cli/main.go" @@ -13,22 +12,24 @@ go build -o "sac-cli" "cli/main.go" # Identify the user's shell SHELL_NAME=$(basename "$SHELL") -COMMAND_NAME="sac-cli" +# # Check if the command is already installed +# COMMAND_NAME="sac-cli" -if command -v "$COMMAND_NAME" >/dev/null 2>&1; then - exit 1 -fi +# if command -v "$COMMAND_NAME" >/dev/null 2>&1; then +# echo "The command '$COMMAND_NAME' is already installed. Please uninstall it before installing a new version." +# exit 1 +# fi # Add sac-cli to the user's PATH -if [[ "$SHELL_NAME" == "zsh" ]]; then - echo "export PATH=\"$SCRIPT_PATH:\$PATH\"" >> ~/.zshrc - source ~/.zshrc -elif [[ "$SHELL_NAME" == "bash" ]]; then - echo "export PATH=\"$SCRIPT_PATH:\$PATH\"" >> ~/.bashrc - source ~/.bashrc +if [[ $SHELL_NAME == "zsh" ]]; then + echo "export PATH=\"$SCRIPT_PATH:\$PATH\"" >>~/.zshrc + source ~/.zshrc +elif [[ $SHELL_NAME == "bash" ]]; then + echo "export PATH=\"$SCRIPT_PATH:\$PATH\"" >>~/.bashrc + source ~/.bashrc else - echo "Unsupported shell: $SHELL_NAME" - exit 1 + echo "Unsupported shell: $SHELL_NAME" + exit 1 fi # Inform the user diff --git a/scripts/clean_old_test_dbs.sh b/scripts/clean_old_test_dbs.sh deleted file mode 100755 index 04c97262b..000000000 --- a/scripts/clean_old_test_dbs.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -PGHOST="127.0.0.1" -PGPORT="5432" -PGUSER="postgres" -PGPASSWORD="postgres" -PGDATABASE="sac" - -DATABASES=$(psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres' AND datname != 'template0' AND datname != '$PGDATABASE' AND datname !='$(whoami)';") - - -for db in $DATABASES; do - echo "Dropping database $db" - psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -c "DROP DATABASE $db;" -done diff --git a/scripts/clean_prefixed_test_dbs.sh b/scripts/clean_prefixed_test_dbs.sh deleted file mode 100755 index 61797e999..000000000 --- a/scripts/clean_prefixed_test_dbs.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -PGHOST="127.0.0.1" -PGPORT="5432" -PGUSER="postgres" -PGPASSWORD="postgres" -PGDATABASE="sac" -PREFIX="sac_test_" - -DATABASES=$(psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname like '$PREFIX%';") - -for db in $DATABASES; do - echo "Dropping database $db" - psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -c "DROP DATABASE $db;" -done diff --git a/scripts/drop_db.sh b/scripts/drop_db.sh deleted file mode 100755 index 878851f59..000000000 --- a/scripts/drop_db.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# PostgreSQL connection parameters -PGHOST="127.0.0.1" -PGPORT="5432" -PGUSER="postgres" -PGPASSWORD="postgres" -PGDATABASE="sac" - -# Check if there are tables to drop -table_count=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -c "SELECT COUNT(*) FROM pg_tables WHERE schemaname = 'public';") -if [ "$table_count" -eq 0 ]; then - echo "No tables to drop. The database is empty." - exit 0 -fi - -echo "Generating DROP TABLE statements..." -if ! psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -c \ - "SELECT 'DROP TABLE IF EXISTS \"' || tablename || '\" CASCADE;' FROM pg_tables WHERE schemaname = 'public';" > drop_tables.sql; then - echo "Error generating DROP TABLE statements." - exit 1 -fi - -echo "Dropping tables..." -if ! psql -q -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -a -f drop_tables.sql > /dev/null 2>&1; then - echo "Error dropping tables." - rm drop_tables.sql - exit 1 -fi - -rm drop_tables.sql - -echo "All tables dropped successfully." \ No newline at end of file diff --git a/scripts/insert_db.sh b/scripts/insert_db.sh deleted file mode 100755 index ded8d2031..000000000 --- a/scripts/insert_db.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PGHOST="127.0.0.1" -PGPORT="5432" -PGUSER="postgres" -PGPASSWORD="postgres" -PGDATABASE="sac" -INSERTSQL="../backend/src/migrations/data.sql" -CHECK_TABLES_QUERY="SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' LIMIT 1);" - -# Change the working directory to the specified location -cd "$SCRIPT_DIR" || { echo "Error: Could not change directory to $SCRIPT_DIR"; exit 1; } - -# Check if tables exist in the database -if psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -t -c "$CHECK_TABLES_QUERY" | grep -q "t"; then - echo "Database $PGDATABASE exists with tables." -else - echo "Error: Database $PGDATABASE does not exist or has no tables. Running database migration." - go run ../backend/src/main.go --only-migrate -fi - -# Insert data from data.sql -if psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -a -f "$INSERTSQL" > /dev/null 2>&1; then - echo "Data inserted successfully." -else - echo "Error: Failed to insert data." - exit 1 -fi