From 3d3afff77668f830b542f9bef34759139e9b7af4 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Thu, 25 Jan 2024 11:24:55 -0500 Subject: [PATCH 01/73] wip --- app.py | 8 ++++++++ resources/User.py | 41 +++++++++++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/app.py b/app.py index 8e0684e7..dd36562c 100644 --- a/app.py +++ b/app.py @@ -2,12 +2,14 @@ from flask_restful import Api from flask_cors import CORS from resources.User import User +from resources.ApiKey import ApiKey from resources.QuickSearch import QuickSearch from resources.DataSources import DataSources from resources.DataSources import DataSourceById from resources.Agencies import Agencies from resources.Archives import Archives from resources.SearchTokens import SearchTokens +from resources.AirtableWebhook import AirtableWebhook from middleware.initialize_psycopg2_connection import initialize_psycopg2_connection psycopg2_connection = initialize_psycopg2_connection() @@ -19,6 +21,11 @@ api.add_resource( User, "/user", resource_class_kwargs={"psycopg2_connection": psycopg2_connection} ) +api.add_resource( + ApiKey, + "/api_key", + resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, +) api.add_resource( QuickSearch, "/quick-search//", @@ -50,5 +57,6 @@ resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, ) + if __name__ == "__main__": app.run(debug=True, host="0.0.0.0") diff --git a/resources/User.py b/resources/User.py index c26725df..7038d1a0 100644 --- a/resources/User.py +++ b/resources/User.py @@ -1,9 +1,6 @@ from werkzeug.security import generate_password_hash, check_password_hash from flask_restful import Resource -from flask import request, jsonify -import uuid -import os -import jwt +from flask import request class User(Resource): @@ -29,14 +26,15 @@ def get(self): else: return {"error": "no match"} if check_password_hash(user_data["password_digest"], password): - api_key = uuid.uuid4().hex - user_id = str(user_data["id"]) - cursor.execute( - "UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id) - ) - payload = {"api_key": api_key} - self.psycopg2_connection.commit() - return payload + return {"data": "Successfully logged in"} + # api_key = uuid.uuid4().hex + # user_id = str(user_data["id"]) + # cursor.execute( + # "UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id) + # ) + # payload = {"api_key": api_key} + # self.psycopg2_connection.commit() + # return payload except Exception as e: self.psycopg2_connection.rollback() @@ -63,3 +61,22 @@ def post(self): self.psycopg2_connection.rollback() print(str(e)) return {"error": e} + + # Endpoint for updating a user's password + def put(self): + try: + data = request.get_json() + email = data.get("email") + password = data.get("password") + password_digest = generate_password_hash(password) + cursor = self.psycopg2_connection.cursor() + cursor.execute( + f"update users set password_digest = '{password_digest}' where email = '{email}'" + ) + self.psycopg2_connection.commit() + return {"data": "Successfully updated password"} + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"error": e} From 2a8ab79bb8b28bc7ff6d76db28334d9bbe3cd00c Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 29 Jan 2024 11:22:11 -0500 Subject: [PATCH 02/73] user login changes --- app.py | 1 - app_test.py | 20 +++++++++++++++++++- do_db_ddl_clean.sql | 5 +++-- regular_api_checks.py | 27 +++++++++++++++++++++++++-- resources/User.py | 39 +++++++++++++-------------------------- 5 files changed, 60 insertions(+), 32 deletions(-) diff --git a/app.py b/app.py index dd36562c..a5617954 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,6 @@ from resources.Agencies import Agencies from resources.Archives import Archives from resources.SearchTokens import SearchTokens -from resources.AirtableWebhook import AirtableWebhook from middleware.initialize_psycopg2_connection import initialize_psycopg2_connection psycopg2_connection = initialize_psycopg2_connection() diff --git a/app_test.py b/app_test.py index 86e34c9c..3d5f8e0d 100644 --- a/app_test.py +++ b/app_test.py @@ -14,7 +14,7 @@ data_source_by_id_results, DATA_SOURCES_APPROVED_COLUMNS, ) - +from middleware.user_queries import user_get_results, user_post_results from middleware.archives_queries import ( archives_get_results, archives_get_query, @@ -125,6 +125,24 @@ def test_data_source_by_id_approved(session): assert not response +def test_user_get_query(session): + curs = session.cursor() + user_data = user_get_results(curs, "test") + + assert user_data["password_digest"] + + +def test_user_post_query(session): + curs = session.cursor() + user_post_results(curs, "unit_test", "unit_test") + + email_check = curs.execute( + f"SELECT email FROM users WHERE email = 'unit_test'" + ).fetchone()[0] + + assert email_check == "unit_test" + + def test_archives_get_results(session): response = archives_get_results(conn=session) diff --git a/do_db_ddl_clean.sql b/do_db_ddl_clean.sql index 714215ce..99e0e5e1 100644 --- a/do_db_ddl_clean.sql +++ b/do_db_ddl_clean.sql @@ -135,7 +135,7 @@ CREATE TABLE if not exists state_names ( ); CREATE TABLE if not exists users ( - id bigint NOT NULL, + id serial primary key, created_at timestamp with time zone, updated_at timestamp with time zone, email text NOT NULL, @@ -163,4 +163,5 @@ INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_u INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (3, 'recUGIoPQbJ6laBmr', 'recv9fMNEQTbVarj2'); INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (4, 'rec8gO2K86yk9mQIU', 'recRvBpZqXM8mjddz'); INSERT INTO state_names VALUES (1, 'IL', 'Illinois'); -INSERT INTO state_names VALUES (2, 'PA', 'Pennsylvania'); \ No newline at end of file +INSERT INTO state_names VALUES (2, 'PA', 'Pennsylvania'); +INSERT INTO users (email, password_digest) VALUES ("test", "test"); \ No newline at end of file diff --git a/regular_api_checks.py b/regular_api_checks.py index aaf049d1..a6202eef 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -150,7 +150,28 @@ def test_get_user(): json={"email": "test2", "password": "test"}, ) - return response + return response.json()["data"] == "Successfully logged in" + + +def test_put_user(): + response = requests.get( + "https://data-sources.pdap.io/api/user", + headers=HEADERS, + json={"email": "test2", "password": "test"}, + ) + + return response.json()["data"] == "Successfully updated password" + + +# api-key +def test_get_api_key(): + response = requests.get( + "https://data-sources.pdap.io/api/api_key", + headers=HEADERS, + json={"email": "test2", "password": "test"}, + ) + + return len(response.json()["api_key"]) > 0 # archives @@ -228,12 +249,14 @@ def main(): "test_quicksearch_media_bulletin_pennsylvania_results", "test_data_source_by_id", "test_data_sources", + "test_update_data_source", "test_data_sources_approved", "test_data_source_by_id_approved", "test_search_tokens_data_sources", "test_search_tokens_data_source_by_id", "test_search_tokens_quick_search_complaints_allegheny_results", - # "test_get_user", + "test_get_user", + "test_get_api_key", "test_get_archives", "test_put_archives", "test_put_archives_brokenasof", diff --git a/resources/User.py b/resources/User.py index 7038d1a0..c9d172ac 100644 --- a/resources/User.py +++ b/resources/User.py @@ -1,58 +1,45 @@ from werkzeug.security import generate_password_hash, check_password_hash from flask_restful import Resource from flask import request +from middleware.user_queries import user_get_results, user_post_results class User(Resource): def __init__(self, **kwargs): self.psycopg2_connection = kwargs["psycopg2_connection"] - # Login function: allows a user to login using their email and password as credentials - # The password is compared to the hashed password stored in the users table - # Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user def get(self): + """ + Login function: allows a user to login using their email and password as credentials + The password is compared to the hashed password stored in the users table + Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user + """ try: data = request.get_json() email = data.get("email") password = data.get("password") cursor = self.psycopg2_connection.cursor() - cursor.execute( - f"select id, password_digest from users where email = '{email}'" - ) - results = cursor.fetchall() - user_data = {} - if len(results) > 0: - user_data = {"id": results[0][0], "password_digest": results[0][1]} - else: - return {"error": "no match"} + + user_data = user_get_results(cursor, email) if check_password_hash(user_data["password_digest"], password): return {"data": "Successfully logged in"} - # api_key = uuid.uuid4().hex - # user_id = str(user_data["id"]) - # cursor.execute( - # "UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id) - # ) - # payload = {"api_key": api_key} - # self.psycopg2_connection.commit() - # return payload except Exception as e: self.psycopg2_connection.rollback() print(str(e)) return {"error": str(e)} - # Sign up function: allows a user to sign up by submitting an email and password. The email and a hashed password are stored in the users table and this data is returned to the user upon completion def post(self): + """ + Sign up function: allows a user to sign up by submitting an email and password. + The email and a hashed password are stored in the users table and this data is returned to the user upon completion + """ try: data = request.get_json() email = data.get("email") password = data.get("password") - password_digest = generate_password_hash(password) cursor = self.psycopg2_connection.cursor() - cursor.execute( - f"insert into users (email, password_digest) values (%s, %s)", - (email, password_digest), - ) + user_post_results(cursor, email, password) self.psycopg2_connection.commit() return {"data": "Successfully added user"} From 8e799b83e875e8c35283c3ba639c9f93b13abd2b Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 29 Jan 2024 13:41:12 -0500 Subject: [PATCH 03/73] missing file --- middleware/user_queries.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 middleware/user_queries.py diff --git a/middleware/user_queries.py b/middleware/user_queries.py new file mode 100644 index 00000000..9a74b622 --- /dev/null +++ b/middleware/user_queries.py @@ -0,0 +1,22 @@ +from werkzeug.security import generate_password_hash + + +def user_get_results(cursor, email): + cursor.execute( + f"select id, password_digest from users where email = '{email}'" + ) + results = cursor.fetchall() + if len(results) > 0: + user_data = {"id": results[0][0], "password_digest": results[0][1]} + return user_data + else: + return {"error": "no match"} + + +def user_post_results(cursor, email, password): + password_digest = generate_password_hash(password) + cursor.execute( + f"insert into users (email, password_digest) values ('{email}', '{password_digest}')" + ) + + return \ No newline at end of file From 7ba090616075a5e2b7b9e8c74cd21d392473cd2e Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 29 Jan 2024 13:41:23 -0500 Subject: [PATCH 04/73] missing file --- middleware/user_queries.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/middleware/user_queries.py b/middleware/user_queries.py index 9a74b622..f42a686b 100644 --- a/middleware/user_queries.py +++ b/middleware/user_queries.py @@ -2,9 +2,7 @@ def user_get_results(cursor, email): - cursor.execute( - f"select id, password_digest from users where email = '{email}'" - ) + cursor.execute(f"select id, password_digest from users where email = '{email}'") results = cursor.fetchall() if len(results) > 0: user_data = {"id": results[0][0], "password_digest": results[0][1]} @@ -19,4 +17,4 @@ def user_post_results(cursor, email, password): f"insert into users (email, password_digest) values ('{email}', '{password_digest}')" ) - return \ No newline at end of file + return From e4c5577680b61a2d9d23972a7b3771139ebc3e83 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 29 Jan 2024 13:43:30 -0500 Subject: [PATCH 05/73] missing file --- resources/ApiKey.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 resources/ApiKey.py diff --git a/resources/ApiKey.py b/resources/ApiKey.py new file mode 100644 index 00000000..7d2d972a --- /dev/null +++ b/resources/ApiKey.py @@ -0,0 +1,36 @@ +from werkzeug.security import check_password_hash +from flask_restful import Resource +from flask import request +from middleware.user_queries import user_get_results +import uuid + + +class ApiKey(Resource): + def __init__(self, **kwargs): + self.psycopg2_connection = kwargs["psycopg2_connection"] + + def get(self): + """ + Generate an API key for a user that successfully logs in + """ + try: + data = request.get_json() + email = data.get("email") + password = data.get("password") + cursor = self.psycopg2_connection.cursor() + user_data = user_get_results(cursor, email) + + if check_password_hash(user_data["password_digest"], password): + api_key = uuid.uuid4().hex + user_id = str(user_data["id"]) + cursor.execute( + "UPDATE users SET api_key = %s WHERE id = %s", (api_key, user_id) + ) + payload = {"api_key": api_key} + self.psycopg2_connection.commit() + return payload + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"error": str(e)} From 18cec795b7c9ff451303e505390f9ccce0824ef7 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Thu, 1 Feb 2024 15:40:35 -0500 Subject: [PATCH 06/73] add user put test --- regular_api_checks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/regular_api_checks.py b/regular_api_checks.py index a6202eef..9bc289ff 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -256,6 +256,7 @@ def main(): "test_search_tokens_data_source_by_id", "test_search_tokens_quick_search_complaints_allegheny_results", "test_get_user", + "test_put_user", "test_get_api_key", "test_get_archives", "test_put_archives", From e6293ef9752df67be7d3b29aa0251323486e6304 Mon Sep 17 00:00:00 2001 From: mbodeantor <96697114+mbodeantor@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:44:36 -0500 Subject: [PATCH 07/73] Dev -> Main (#202) * Add last cached value to data source query * test: update snapshots * fix(assets): favicon not displaying * feat(pages): update DataSourceStaticView Fix url breaking, add support for last archived, add button to go to Internet Archive * test(pages): update tests and snapshots Update test for DataSourceStaticView, update various snapshots * chore(lint): reformat files with lint errors using black * chore(lint): attempt re-formatting again with actual VS 'black' extension * chore: update .gitignore * refactor(util): update formatDate to return undefined for invalid date passed * test(util): update test for formatDate * get_data_source_by_id fix * chore(deps): add eslint-config from design-system * test(scripts): update test scripts * docs(readme): document lint/test script updates * refactor: update miscellaneous files with linting errors * test(snapshots): update outdated snapshots * ci(client): add client scripts to pull workflow * chore(config): remove stray eslint config from package.json * ci: move working-directory to top level defaults * ci: add cache-dependency-path to setup-node action * ci: add cache-dependency-path to all steps * chore(linting): remove extraneous rules in linting config update files per update * chore(scripts): update ci script to use proper flag * Revert "test(snapshots): update outdated snapshots" This reverts commit 386d09b8c89b1b709e01190d2d3115a2e541e7f1. * test: update snapshots again * ci: use exact node version used locally * Revert "ci: use exact node version used locally" This reverts commit e1c7b7332ea59139ca36b4de1b1a42a32bfa291a. * chore(deps): re-install deps with node v20 * ci: use node v20 * test: update snapshots * ci: add time zone setter to test script * feature: add tertiary button from design-system * test(pages): update DataSourceStaticView test * chore(deps): bump design-system -> 2.2.0 * fix: miscellaneous styling issues * test: update snapshots * chore(deps): bump design-system -> 2.3.0 * chore(cleanup): remove logs and miscellaneous updates * test: update snapshots * remove agencies join from archives endpoint, change url_status when updating broken url * update test columns * standardized error codes --------- Co-authored-by: kalenluciano <103911842+kalenluciano@users.noreply.github.com> Co-authored-by: Joshua Graber Co-authored-by: Joshua Graber <68428039+joshuagraber@users.noreply.github.com> Co-authored-by: Marty Bode --- .github/workflows/pull.yaml | 90 +- README.md | 12 +- app_test_data.py | 2 - client/.eslintrc.json | 39 +- client/.gitignore | 2 +- client/index.html | 1 + client/package-lock.json | 2809 +++++++---------- client/package.json | 33 +- client/public/{ => assets}/favicon.png | Bin .../searchResultCard.test.js.snap | 6 +- .../__tests__/searchResultCard.test.js | 94 +- client/src/main.css | 10 +- client/src/main.js | 14 +- client/src/pages/DataSourceStaticView.vue | 117 +- client/src/pages/QuickSearchPage.vue | 4 +- client/src/pages/SearchResultPage.vue | 26 +- client/src/pages/__mocks__/index.js | 161 +- .../__tests__/__snapshots__/app.test.js.snap | 2 +- .../dataSourceStaticView.test.js.snap | 243 +- .../searchResultPage.test.js.snap | 8 +- client/src/pages/__tests__/app.test.js | 14 +- .../__tests__/dataSourceStaticView.test.js | 52 +- .../pages/__tests__/quickSearchPage.test.js | 10 +- .../pages/__tests__/searchResultPage.test.js | 40 +- client/src/pages/util.js | 129 +- client/src/util/__tests__/formatDate.test.js | 56 +- client/src/util/__tests__/pluralize.test.js | 26 +- client/src/util/formatDate.js | 7 +- client/src/util/links.js | 36 +- client/src/util/pluralize.js | 2 +- client/tools/testing/serializer.js | 8 +- client/tools/testing/setup.js | 4 +- middleware/archives_queries.py | 22 +- middleware/data_source_queries.py | 1 + resources/ApiKey.py | 39 + resources/DataSources.py | 16 +- resources/QuickSearch.py | 6 + resources/SearchTokens.py | 8 +- resources/User.py | 16 +- 39 files changed, 1985 insertions(+), 2180 deletions(-) rename client/public/{ => assets}/favicon.png (100%) create mode 100644 resources/ApiKey.py diff --git a/.github/workflows/pull.yaml b/.github/workflows/pull.yaml index e5ebd9ec..94c5f0a1 100644 --- a/.github/workflows/pull.yaml +++ b/.github/workflows/pull.yaml @@ -4,7 +4,8 @@ on: pull_request: jobs: - setup: + setup_api: + name: Setup API runs-on: ubuntu-latest steps: - name: Cancel previous @@ -21,13 +22,15 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - lint: + lint_api: + name: Lint API runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: psf/black@stable - test: + test_api: + name: Test API runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -44,3 +47,84 @@ jobs: run: | pip install pytest pytest-cov pytest app_test.py --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html + + setup_client: + defaults: + run: + working-directory: client + name: Setup client + runs-on: ubuntu-latest + steps: + - name: Cancel previous + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + cache-dependency-path: 'client/package-lock.json' + - name: Install deps + run: npm ci + + + lint_client: + defaults: + run: + working-directory: client + name: Lint client + needs: setup_client + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + cache-dependency-path: 'client/package-lock.json' + - run: npm ci + - name: Lint JS and Vue + run: npm run lint + + test_client: + defaults: + run: + working-directory: client + name: Test client + needs: setup_client + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + cache-dependency-path: 'client/package-lock.json' + - uses: szenius/set-timezone@v1.2 + with: + timezoneLinux: "America/New_York" + - run: npm ci + - name: Run tests + run: npm run test:ci + + build_client: + defaults: + run: + working-directory: client + name: Build client + needs: setup_client + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + cache-dependency-path: 'client/package-lock.json' + - run: npm ci + - name: Build app + run: npm run build \ No newline at end of file diff --git a/README.md b/README.md index e93f242c..0c297112 100644 --- a/README.md +++ b/README.md @@ -137,11 +137,21 @@ npm run lint npm run lint:fix ``` -### Runs tests quietly +### Runs tests with debug output ``` npm run test ``` +### Runs tests quietly for CI +``` +npm run test:ci +``` + +### Runs tests only on changed files +``` +npm run test:changed +``` + ### Runs tests and outputs coverage reports ``` npm run coverage diff --git a/app_test_data.py b/app_test_data.py index dd0b0035..12c4b414 100644 --- a/app_test_data.py +++ b/app_test_data.py @@ -385,13 +385,11 @@ "https://informationportal.igchicago.org/911-calls-for-cpd-service/", "Bi-weekly", "NULL", - "Chicago Police Department - IL", ), ( "rec04V1oMa6Dxt0Sl", "https://www.columbus.gov/police-annualreports/", "Annually", datetime.date(2023, 9, 16), - "Columbus Division of Police - OH", ), ] diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 7f066533..23a0e9ea 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -1,38 +1,9 @@ { "root": true, "extends": [ - "eslint:recommended", - "plugin:vue/vue3-essential", - "plugin:vue/vue3-recommended", - "plugin:vue/strongly-recommended", - "@vue/eslint-config-prettier" + "@pdap-design-system/eslint-config" ], - "plugins": ["prettier"], - "rules": { - "vue/require-default-prop": "off", - // Switch base eslint indent rule off - "indent": "off", - "vue/html-self-closing": [ - "error", - { - "html": { - "void": "always", - "normal": "always", - "component": "always" - }, - "svg": "always", - "math": "always" - } - ], - "prettier/prettier": [ - // Use prettier indent rule instead - "warn", - { - "indent": ["warn", "tab", { "SwitchCase": 2 }], - "tabWidth": 2, - "useTabs": true - } - ], - "vue/no-multiple-template-root": "off" - } -} + "plugins": [ + "prettier" + ] +} \ No newline at end of file diff --git a/client/.gitignore b/client/.gitignore index 65bb91c3..27a96739 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,6 +1,6 @@ .DS_Store node_modules -/dist +dist coverage # local env files diff --git a/client/index.html b/client/index.html index 7f8e39b7..a8b2a20e 100644 --- a/client/index.html +++ b/client/index.html @@ -4,6 +4,7 @@ + =8.0.0" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", @@ -542,22 +530,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -578,11 +566,55 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -636,27 +668,15 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -704,19 +724,30 @@ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", "dev": true }, - "node_modules/@pkgr/utils": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", - "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "node_modules/@pdap-design-system/eslint-config": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdap-design-system/eslint-config/-/eslint-config-1.0.1.tgz", + "integrity": "sha512-cHEUHlR2WuQUkjbdstO15HllUKgMA2tdPs0tSd+i6YdqPUjsx+CBktNA82eLrFxVsd5dhGt8EA5Co8fwyiur1A==", + "dev": true, + "peerDependencies": { + "eslint": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.3.0", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.6.0" - }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -725,9 +756,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz", - "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", + "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", "cpu": [ "arm" ], @@ -738,9 +769,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz", - "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", + "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", "cpu": [ "arm64" ], @@ -751,9 +782,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz", - "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", + "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", "cpu": [ "arm64" ], @@ -764,9 +795,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz", - "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", + "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", "cpu": [ "x64" ], @@ -777,9 +808,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz", - "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", + "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", "cpu": [ "arm" ], @@ -790,9 +821,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz", - "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", + "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", "cpu": [ "arm64" ], @@ -803,9 +834,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz", - "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", + "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", "cpu": [ "arm64" ], @@ -816,9 +847,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz", - "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", + "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", "cpu": [ "riscv64" ], @@ -829,9 +860,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", - "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", + "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", "cpu": [ "x64" ], @@ -842,9 +873,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz", - "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", + "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", "cpu": [ "x64" ], @@ -855,9 +886,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz", - "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", + "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", "cpu": [ "arm64" ], @@ -868,9 +899,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz", - "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", + "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", "cpu": [ "ia32" ], @@ -881,9 +912,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", - "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", + "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", "cpu": [ "x64" ], @@ -899,15 +930,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -926,18 +948,6 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.1.tgz", - "integrity": "sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -959,19 +969,14 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/node": { - "version": "20.4.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", - "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==", - "dev": true + "version": "20.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz", + "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/qs": { "version": "6.9.11", @@ -986,15 +991,15 @@ "dev": true }, "node_modules/@vitejs/plugin-vue": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", - "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", "dev": true, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.0.0", + "vite": "^4.0.0 || ^5.0.0", "vue": "^3.2.25" } }, @@ -1026,13 +1031,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.0.4.tgz", - "integrity": "sha512-/NRN9N88qjg3dkhmFcCBwhn/Ie4h064pY3iv7WLRsDJW7dXnEgeoa8W9zy7gIPluhz6CkgqiB3HmpIXgmEY5dQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.1.tgz", + "integrity": "sha512-/bqGXcHfyKgFWYwIgFr1QYDaR9e64pRKxgBNWNXPefPFRhgm+K3+a/dS0cUGEreWngets3dlr8w8SBRw2fCfFQ==", "dev": true, "dependencies": { - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", "chai": "^4.3.10" }, "funding": { @@ -1040,12 +1045,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.0.4.tgz", - "integrity": "sha512-rhOQ9FZTEkV41JWXozFM8YgOqaG9zA7QXbhg5gy6mFOVqh4PcupirIJ+wN7QjeJt8S8nJRYuZH1OjJjsbxAXTQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.1.tgz", + "integrity": "sha512-zc2dP5LQpzNzbpaBt7OeYAvmIsRS1KpZQw4G3WM/yqSV1cQKNKwLGmnm79GyZZjMhQGlRcSFMImLjZaUQvNVZQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.0.4", + "@vitest/utils": "1.2.1", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -1068,10 +1073,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@vitest/snapshot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.0.4.tgz", - "integrity": "sha512-vkfXUrNyNRA/Gzsp2lpyJxh94vU2OHT1amoD6WuvUAA12n32xeVZQ0KjjQIf8F6u7bcq2A2k969fMVxEsxeKYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", + "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1083,9 +1100,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.0.4.tgz", - "integrity": "sha512-9ojTFRL1AJVh0hvfzAQpm0QS6xIS+1HFIw94kl/1ucTfGCaj1LV/iuJU4Y6cdR03EzPDygxTHwE1JOm+5RCcvA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.1.tgz", + "integrity": "sha512-vG3a/b7INKH7L49Lbp0IWrG6sw9j4waWAucwnksPB1r1FTJgV7nkBByd9ufzu6VWya/QTvQW4V9FShZbZIB2UQ==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -1095,12 +1112,13 @@ } }, "node_modules/@vitest/utils": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.0.4.tgz", - "integrity": "sha512-gsswWDXxtt0QvtK/y/LWukN7sGMYmnCcv1qv05CsY6cU/Y1zpGX1QuvLs+GO1inczpE6Owixeel3ShkjhYtGfA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-bsH6WVZYe/J2v3+81M5LDU8kW76xWObKIURpPrOXm2pjBniBu2MERI/XP60GpS4PHU3jyK50LUutOwrx4CyHUg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", "loupe": "^2.3.7", "pretty-format": "^29.7.0" }, @@ -1109,55 +1127,65 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.11.tgz", - "integrity": "sha512-h97/TGWBilnLuRaj58sxNrsUU66fwdRKLOLQ9N/5iNDfp+DZhYH9Obhe0bXxhedl8fjAgpRANpiZfbgWyruQ0w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", + "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", "dependencies": { - "@babel/parser": "^7.23.5", - "@vue/shared": "3.3.11", + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.15", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" } }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, "node_modules/@vue/compiler-dom": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.11.tgz", - "integrity": "sha512-zoAiUIqSKqAJ81WhfPXYmFGwDRuO+loqLxvXmfUdR5fOitPoUiIeFI9cTTyv9MU5O1+ZZglJVTusWzy+wfk5hw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", + "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", "dependencies": { - "@vue/compiler-core": "3.3.11", - "@vue/shared": "3.3.11" + "@vue/compiler-core": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.11.tgz", - "integrity": "sha512-U4iqPlHO0KQeK1mrsxCN0vZzw43/lL8POxgpzcJweopmqtoYy9nljJzWDIQS3EfjiYhfdtdk9Gtgz7MRXnz3GA==", - "dependencies": { - "@babel/parser": "^7.23.5", - "@vue/compiler-core": "3.3.11", - "@vue/compiler-dom": "3.3.11", - "@vue/compiler-ssr": "3.3.11", - "@vue/reactivity-transform": "3.3.11", - "@vue/shared": "3.3.11", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", + "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.15", + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15", "estree-walker": "^2.0.2", "magic-string": "^0.30.5", - "postcss": "^8.4.32", + "postcss": "^8.4.33", "source-map-js": "^1.0.2" } }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.11.tgz", - "integrity": "sha512-Zd66ZwMvndxRTgVPdo+muV4Rv9n9DwQ4SSgWWKWkPFebHQfVYRrVjeygmmDmPewsHyznCNvJ2P2d6iOOhdv8Qg==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", + "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", "dependencies": { - "@vue/compiler-dom": "3.3.11", - "@vue/shared": "3.3.11" + "@vue/compiler-dom": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", - "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", + "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" }, "node_modules/@vue/eslint-config-prettier": { "version": "8.0.0", @@ -1174,69 +1202,57 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.11.tgz", - "integrity": "sha512-D5tcw091f0nuu+hXq5XANofD0OXnBmaRqMYl5B3fCR+mX+cXJIGNw/VNawBqkjLNWETrFW0i+xH9NvDbTPVh7g==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", + "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", "dependencies": { - "@vue/shared": "3.3.11" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.11.tgz", - "integrity": "sha512-fPGjH0wqJo68A0wQ1k158utDq/cRyZNlFoxGwNScE28aUFOKFEnCBsvyD8jHn+0kd0UKVpuGuaZEQ6r9FJRqCg==", - "dependencies": { - "@babel/parser": "^7.23.5", - "@vue/compiler-core": "3.3.11", - "@vue/shared": "3.3.11", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.5" + "@vue/shared": "3.4.15" } }, "node_modules/@vue/runtime-core": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.11.tgz", - "integrity": "sha512-g9ztHGwEbS5RyWaOpXuyIVFTschclnwhqEbdy5AwGhYOgc7m/q3NFwr50MirZwTTzX55JY8pSkeib9BX04NIpw==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", + "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", "dependencies": { - "@vue/reactivity": "3.3.11", - "@vue/shared": "3.3.11" + "@vue/reactivity": "3.4.15", + "@vue/shared": "3.4.15" } }, "node_modules/@vue/runtime-dom": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.11.tgz", - "integrity": "sha512-OlhtV1PVpbgk+I2zl+Y5rQtDNcCDs12rsRg71XwaA2/Rbllw6mBLMi57VOn8G0AjOJ4Mdb4k56V37+g8ukShpQ==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", + "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", "dependencies": { - "@vue/runtime-core": "3.3.11", - "@vue/shared": "3.3.11", - "csstype": "^3.1.2" + "@vue/runtime-core": "3.4.15", + "@vue/shared": "3.4.15", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.11.tgz", - "integrity": "sha512-AIWk0VwwxCAm4wqtJyxBylRTXSy1wCLOKbWxHaHiu14wjsNYtiRCSgVuqEPVuDpErOlRdNnuRgipQfXRLjLN5A==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", + "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", "dependencies": { - "@vue/compiler-ssr": "3.3.11", - "@vue/shared": "3.3.11" + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15" }, "peerDependencies": { - "vue": "3.3.11" + "vue": "3.4.15" } }, "node_modules/@vue/shared": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.11.tgz", - "integrity": "sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw==" + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", + "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==" }, "node_modules/@vue/test-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.1.tgz", - "integrity": "sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.4.tgz", + "integrity": "sha512-8jkRxz8pNhClAf4Co4ZrpAoFISdvT3nuSkUlY6Ys6rmTpw3DMWG/X3mw3gQ7QJzgCZO9f+zuE2kW57fi09MW7Q==", "dev": true, "dependencies": { - "js-beautify": "1.14.9", - "vue-component-type-helpers": "1.8.4" + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^1.8.21" }, "peerDependencies": { "@vue/server-renderer": "^3.0.1", @@ -1248,17 +1264,102 @@ } } }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true + "node_modules/@vuelidate/core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz", + "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==", + "dev": true, + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/core/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz", + "integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==", + "dev": true, + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/acorn": { "version": "8.11.3", @@ -1290,18 +1391,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1394,9 +1483,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "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": [ { @@ -1413,9 +1502,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" @@ -1431,11 +1520,11 @@ } }, "node_modules/axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -1446,15 +1535,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1470,18 +1550,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, - "node_modules/bplist-parser": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", - "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.44" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1542,21 +1610,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/bundle-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", - "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", - "dev": true, - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1599,9 +1652,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001568", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", - "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", + "version": "1.0.30001580", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", + "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", "dev": true, "funding": [ { @@ -1642,13 +1695,29 @@ "node": ">=4" } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" }, "engines": { "node": "*" @@ -1738,6 +1807,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1768,12 +1849,12 @@ } }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": ">= 10" + "node": ">=14" } }, "node_modules/concat-map": { @@ -1925,62 +2006,11 @@ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "dev": true }, - "node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", - "dev": true, - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/data-urls": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/data-urls/node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dev": true, - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", - "dev": true, - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1998,12 +2028,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -2022,40 +2046,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/default-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", - "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", - "dev": true, - "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", - "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", - "dev": true, - "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", @@ -2070,18 +2060,6 @@ "node": ">= 0.4" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2149,18 +2127,6 @@ } ] }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", @@ -2190,6 +2156,12 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/editorconfig": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", @@ -2217,15 +2189,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/editorconfig/node_modules/minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", @@ -2242,16 +2205,21 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.609", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", - "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==", + "version": "1.4.647", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz", + "integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -2305,16 +2273,28 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2373,23 +2353,24 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", - "integrity": "sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", "dev": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "synckit": "^0.8.6" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/prettier" + "url": "https://opencollective.com/eslint-plugin-prettier" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", + "eslint-config-prettier": "*", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -2402,43 +2383,26 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz", - "integrity": "sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==", + "version": "9.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.20.1.tgz", + "integrity": "sha512-GyCs8K3lkEvoyC1VV97GJhP1SvqsKCiWGHnbn0gVUYiUhaH2+nB+Dv1uekv1THFMPbBfYxukrzQdltw950k+LQ==", "dev": true, "dependencies": { - "eslint-utils": "^3.0.0", + "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", - "nth-check": "^2.0.1", - "postcss-selector-parser": "^6.0.9", - "semver": "^7.3.5", - "vue-eslint-parser": "^8.0.1" + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.4.0", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/eslint-plugin-vue/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2456,43 +2420,6 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", @@ -2504,18 +2431,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2533,18 +2448,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -2579,9 +2482,13 @@ } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", @@ -2593,23 +2500,23 @@ } }, "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -2643,6 +2550,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2656,9 +2575,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2705,12 +2624,13 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { @@ -2718,15 +2638,15 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -2742,6 +2662,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2845,53 +2781,79 @@ } }, "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "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" + "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": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob/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/glob/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/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2928,17 +2890,17 @@ "dev": true }, "node_modules/happy-dom": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-13.1.4.tgz", - "integrity": "sha512-f8STa4iuJcpXn7YjgqBEemzinyPAdjlHMxlCNbIERdRIjJO9Z9Cj3XW5LuiEhsURFfl0AOWqj0hQitme4gq+Gg==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-13.3.1.tgz", + "integrity": "sha512-KIlztn+nRWstprUyI3Wzy1UJrg72uOaoo4SaBLNrV6xrn2Rq86eQruKOL7ZyDhkfou3nEZX6rgRYtvsqwMInvQ==", "dev": true, "dependencies": { - "css.escape": "^1.5.1", "entities": "^4.5.0", - "iconv-lite": "^0.6.3", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0" } }, "node_modules/has-flag": { @@ -3007,18 +2969,6 @@ "he": "bin/he" } }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3059,20 +3009,6 @@ "node": ">=6.0.0" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-response-object": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", @@ -3088,26 +3024,13 @@ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=14.18.0" + "node": ">=16.17.0" } }, "node_modules/iconv-lite": { @@ -3202,21 +3125,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3226,6 +3134,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3238,24 +3155,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3274,12 +3173,6 @@ "node": ">=8" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -3292,33 +3185,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3381,6 +3247,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-serializer-vue-tjw": { "version": "3.20.0", "resolved": "https://registry.npmjs.org/jest-serializer-vue-tjw/-/jest-serializer-vue-tjw-3.20.0.tgz", @@ -3407,15 +3291,15 @@ } }, "node_modules/js-beautify": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", - "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", + "version": "1.14.11", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", + "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", "dev": true, "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.3", - "glob": "^8.1.0", - "nopt": "^6.0.0" + "glob": "^10.3.3", + "nopt": "^7.2.0" }, "bin": { "css-beautify": "js/bin/css-beautify.js", @@ -3423,47 +3307,7 @@ "js-beautify": "js/bin/js-beautify.js" }, "engines": { - "node": ">=12" - } - }, - "node_modules/js-beautify/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/js-beautify/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-beautify/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/js-yaml": { @@ -3478,93 +3322,11 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdom": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", - "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", - "decimal.js": "^10.4.3", - "domexception": "^4.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dev": true, - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", - "dev": true, - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jsdom/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -3579,9 +3341,9 @@ "dev": true }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -3596,6 +3358,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3683,15 +3454,12 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": "14 || >=16.14" } }, "node_modules/magic-string": { @@ -3817,6 +3585,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mlly": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", @@ -3896,18 +3673,18 @@ "dev": true }, "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", "dev": true, "dependencies": { - "abbrev": "^1.0.0" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-path": { @@ -3929,9 +3706,9 @@ } }, "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -3967,12 +3744,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", - "dev": true - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4024,24 +3795,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", - "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", - "dev": true, - "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4074,18 +3827,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-limit/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", @@ -4177,6 +3918,22 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -4193,9 +3950,9 @@ } }, "node_modules/pdap-design-system": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/pdap-design-system/-/pdap-design-system-2.1.7.tgz", - "integrity": "sha512-Vh3R7xAWE5mlDtZcJVHBWwwYvQ8MVS43Wd/G6XB40XqjqSaH2AzE97J0nRehedDEb+Ko7/3yIos0VlmPqYalTQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pdap-design-system/-/pdap-design-system-2.3.0.tgz", + "integrity": "sha512-wTYw/kSrQ39f8u5Z66C37kMmlKZDSm1jtY4LswIGWpWx40xh6rt2MRj5d4A0PxCIm9ftRzLqmxQ4ykfh0Th49A==", "dev": true, "dependencies": { "@vuelidate/core": "^2.0.3", @@ -4214,94 +3971,6 @@ "npm": ">=8.19.3" } }, - "node_modules/pdap-design-system/node_modules/@vuelidate/core": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz", - "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==", - "dev": true, - "dependencies": { - "vue-demi": "^0.13.11" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^2.0.0 || >=3.0.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/pdap-design-system/node_modules/@vuelidate/core/node_modules/vue-demi": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", - "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/pdap-design-system/node_modules/@vuelidate/validators": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz", - "integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==", - "dev": true, - "dependencies": { - "vue-demi": "^0.13.11" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^2.0.0 || >=3.0.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/pdap-design-system/node_modules/@vuelidate/validators/node_modules/vue-demi": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", - "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/pdap-design-system/node_modules/happy-dom": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-6.0.4.tgz", @@ -4364,9 +4033,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", @@ -4490,9 +4159,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4518,9 +4187,9 @@ } }, "node_modules/prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "peer": true, "bin": { @@ -4597,16 +4266,10 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -4627,12 +4290,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4695,12 +4352,6 @@ "node": ">=8.10.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4752,6 +4403,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -4768,116 +4439,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", - "dev": true - }, - "node_modules/run-applescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", - "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4913,18 +4474,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -4940,6 +4489,18 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "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" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/set-function-length": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", @@ -4998,10 +4559,16 @@ "dev": true }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/source-map": { "version": "0.6.1", @@ -5020,18 +4587,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5050,10 +4605,88 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -5102,14 +4735,14 @@ } }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -5120,7 +4753,7 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/sucrase/node_modules/commander": { @@ -5132,26 +4765,6 @@ "node": ">= 6" } }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5177,17 +4790,17 @@ } }, "node_modules/svgo": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.5.tgz", - "integrity": "sha512-HQKHEo73pMNOlDlBcLgZRcHW2+1wo7bFYayAXkGN0l/2+h68KjlfZyMRhdhaGvoHV2eApOovl12zoFz42sT6rQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", - "css-tree": "^2.2.1", + "css-tree": "^2.3.1", "css-what": "^6.1.0", - "csso": "5.0.5", + "csso": "^5.0.5", "picocolors": "^1.0.0" }, "bin": { @@ -5201,11 +4814,14 @@ "url": "https://opencollective.com/svgo" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } }, "node_modules/sync-request": { "version": "6.1.0", @@ -5231,12 +4847,12 @@ } }, "node_modules/synckit": { - "version": "0.8.6", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", - "integrity": "sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==", + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "dependencies": { - "@pkgr/utils": "^2.4.2", + "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" }, "engines": { @@ -5247,9 +4863,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", - "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5283,46 +4899,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/terser": { - "version": "5.26.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", - "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5337,6 +4913,26 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5413,9 +5009,9 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", - "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", + "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", "dev": true, "engines": { "node": ">=14.0.0" @@ -5430,18 +5026,6 @@ "node": ">=14.0.0" } }, - "node_modules/titleize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", - "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5463,30 +5047,6 @@ "node": ">=8.0" } }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -5550,6 +5110,12 @@ "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -5559,15 +5125,6 @@ "node": ">= 10.0.0" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -5607,16 +5164,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5638,9 +5185,9 @@ } }, "node_modules/vite": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz", - "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -5693,9 +5240,9 @@ } }, "node_modules/vite-node": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.0.4.tgz", - "integrity": "sha512-9xQQtHdsz5Qn8hqbV7UKqkm8YkJhzT/zr41Dmt5N7AlD8hJXw/Z7y0QiD5I8lnTthV9Rvcvi0QW7PI0Fq83ZPg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.1.tgz", + "integrity": "sha512-fNzHmQUSOY+y30naohBvSW7pPn/xn3Ib/uqm+5wAJQJiqQsU0NBR78XdRJb04l4bOFKjpTWld0XAfkKlrDbySg==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -5715,9 +5262,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -5731,9 +5278,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -5747,9 +5294,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -5763,9 +5310,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -5779,9 +5326,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -5795,9 +5342,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -5811,9 +5358,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -5827,9 +5374,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -5843,9 +5390,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -5859,9 +5406,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -5875,9 +5422,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -5891,9 +5438,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -5907,9 +5454,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -5923,9 +5470,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -5939,9 +5486,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -5955,9 +5502,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -5971,9 +5518,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -5987,9 +5534,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -6003,9 +5550,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -6019,9 +5566,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -6035,9 +5582,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -6051,9 +5598,9 @@ } }, "node_modules/vite-node/node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -6067,9 +5614,9 @@ } }, "node_modules/vite-node/node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -6079,35 +5626,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/vite-node/node_modules/rollup": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", - "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -6120,26 +5667,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.5", - "@rollup/rollup-android-arm64": "4.9.5", - "@rollup/rollup-darwin-arm64": "4.9.5", - "@rollup/rollup-darwin-x64": "4.9.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", - "@rollup/rollup-linux-arm64-gnu": "4.9.5", - "@rollup/rollup-linux-arm64-musl": "4.9.5", - "@rollup/rollup-linux-riscv64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-musl": "4.9.5", - "@rollup/rollup-win32-arm64-msvc": "4.9.5", - "@rollup/rollup-win32-ia32-msvc": "4.9.5", - "@rollup/rollup-win32-x64-msvc": "4.9.5", + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", "fsevents": "~2.3.2" } }, "node_modules/vite-node/node_modules/vite": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", - "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -6204,17 +5751,17 @@ } }, "node_modules/vitest": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.0.4.tgz", - "integrity": "sha512-s1GQHp/UOeWEo4+aXDOeFBJwFzL6mjycbQwwKWX2QcYfh/7tIerS59hWQ20mxzupTJluA2SdwiBuWwQHH67ckg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.1.tgz", + "integrity": "sha512-TRph8N8rnSDa5M2wKWJCMnztCZS9cDcgVTQ6tsTFTG/odHJ4l5yNVqvbeDJYJRZ6is3uxaEpFs8LL6QM+YFSdA==", "dev": true, "dependencies": { - "@vitest/expect": "1.0.4", - "@vitest/runner": "1.0.4", - "@vitest/snapshot": "1.0.4", - "@vitest/spy": "1.0.4", - "@vitest/utils": "1.0.4", - "acorn-walk": "^8.3.0", + "@vitest/expect": "1.2.1", + "@vitest/runner": "1.2.1", + "@vitest/snapshot": "1.2.1", + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", + "acorn-walk": "^8.3.2", "cac": "^6.7.14", "chai": "^4.3.10", "debug": "^4.3.4", @@ -6228,7 +5775,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.1", "vite": "^5.0.0", - "vite-node": "1.0.4", + "vite-node": "1.2.1", "why-is-node-running": "^2.2.2" }, "bin": { @@ -6270,9 +5817,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", - "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -6286,9 +5833,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", - "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -6302,9 +5849,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", - "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -6318,9 +5865,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", - "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -6334,9 +5881,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", - "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -6350,9 +5897,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", - "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -6366,9 +5913,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", - "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -6382,9 +5929,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", - "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -6398,9 +5945,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", - "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -6414,9 +5961,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", - "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -6430,9 +5977,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", - "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -6446,9 +5993,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", - "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -6462,9 +6009,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", - "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -6478,9 +6025,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", - "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -6494,9 +6041,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", - "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -6510,9 +6057,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", - "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -6526,9 +6073,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", - "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -6542,9 +6089,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", - "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -6558,9 +6105,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", - "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -6574,9 +6121,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", - "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -6590,9 +6137,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", - "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -6606,9 +6153,9 @@ } }, "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", - "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -6622,9 +6169,9 @@ } }, "node_modules/vitest/node_modules/esbuild": { - "version": "0.19.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", - "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -6634,79 +6181,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.11", - "@esbuild/android-arm": "0.19.11", - "@esbuild/android-arm64": "0.19.11", - "@esbuild/android-x64": "0.19.11", - "@esbuild/darwin-arm64": "0.19.11", - "@esbuild/darwin-x64": "0.19.11", - "@esbuild/freebsd-arm64": "0.19.11", - "@esbuild/freebsd-x64": "0.19.11", - "@esbuild/linux-arm": "0.19.11", - "@esbuild/linux-arm64": "0.19.11", - "@esbuild/linux-ia32": "0.19.11", - "@esbuild/linux-loong64": "0.19.11", - "@esbuild/linux-mips64el": "0.19.11", - "@esbuild/linux-ppc64": "0.19.11", - "@esbuild/linux-riscv64": "0.19.11", - "@esbuild/linux-s390x": "0.19.11", - "@esbuild/linux-x64": "0.19.11", - "@esbuild/netbsd-x64": "0.19.11", - "@esbuild/openbsd-x64": "0.19.11", - "@esbuild/sunos-x64": "0.19.11", - "@esbuild/win32-arm64": "0.19.11", - "@esbuild/win32-ia32": "0.19.11", - "@esbuild/win32-x64": "0.19.11" - } - }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz", - "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==", + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -6719,38 +6222,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.5", - "@rollup/rollup-android-arm64": "4.9.5", - "@rollup/rollup-darwin-arm64": "4.9.5", - "@rollup/rollup-darwin-x64": "4.9.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.5", - "@rollup/rollup-linux-arm64-gnu": "4.9.5", - "@rollup/rollup-linux-arm64-musl": "4.9.5", - "@rollup/rollup-linux-riscv64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-gnu": "4.9.5", - "@rollup/rollup-linux-x64-musl": "4.9.5", - "@rollup/rollup-win32-arm64-msvc": "4.9.5", - "@rollup/rollup-win32-ia32-msvc": "4.9.5", - "@rollup/rollup-win32-x64-msvc": "4.9.5", + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", "fsevents": "~2.3.2" } }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/vitest/node_modules/vite": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz", - "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -6803,15 +6294,15 @@ } }, "node_modules/vue": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.11.tgz", - "integrity": "sha512-d4oBctG92CRO1cQfVBZp6WJAs0n8AK4Xf5fNjQCBeKCvMI1efGQ5E3Alt1slFJS9fZuPcFoiAiqFvQlv1X7t/w==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", + "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", "dependencies": { - "@vue/compiler-dom": "3.3.11", - "@vue/compiler-sfc": "3.3.11", - "@vue/runtime-dom": "3.3.11", - "@vue/server-renderer": "3.3.11", - "@vue/shared": "3.3.11" + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-sfc": "3.4.15", + "@vue/runtime-dom": "3.4.15", + "@vue/server-renderer": "3.4.15", + "@vue/shared": "3.4.15" }, "peerDependencies": { "typescript": "*" @@ -6823,27 +6314,27 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.4.tgz", - "integrity": "sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==", + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.27.tgz", + "integrity": "sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==", "dev": true }, "node_modules/vue-eslint-parser": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", - "integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", + "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", "dev": true, "dependencies": { - "debug": "^4.3.2", - "eslint-scope": "^7.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.0.0", + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", - "semver": "^7.3.5" + "semver": "^7.3.6" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^14.17.0 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" @@ -6852,18 +6343,6 @@ "eslint": ">=6.0.0" } }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/vue-router": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", @@ -6878,18 +6357,6 @@ "vue": "^3.2.0" } }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6967,6 +6434,100 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6982,12 +6543,6 @@ "node": ">=12" } }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -7004,12 +6559,12 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/client/package.json b/client/package.json index dfb1c7fe..a2504e2f 100644 --- a/client/package.json +++ b/client/package.json @@ -6,10 +6,12 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint", - "lint:fix": "eslint --fix", - "test": "vitest", - "coverage": "vitest run --coverage" + "lint": "eslint src --ext .js .", + "lint:fix": "npm run lint -- --fix", + "test": "vitest --dom --run", + "test:ci": "npm run test -- --silent", + "test:changed": "npm run test -- --dom --changed", + "coverage": "npm run test -- --coverage" }, "dependencies": { "axios": "^1.6.0", @@ -17,37 +19,22 @@ "vue-router": "^4.2.4" }, "devDependencies": { + "@pdap-design-system/eslint-config": "^1.0.1", "@vitejs/plugin-vue": "^4.2.3", "@vitest/coverage-v8": "^1.2.1", "@vue/eslint-config-prettier": "^8.0.0", "@vue/test-utils": "^2.4.1", "autoprefixer": "^10.4.16", "eslint": "^8.0.0", - "eslint-plugin-vue": "^8.0.3", + "eslint-plugin-vue": "^9.20.1", "happy-dom": "^13.1.4", "jest-serializer-vue-tjw": "^3.20.0", - "jsdom": "^22.1.0", - "pdap-design-system": "^2.1.7", + "pdap-design-system": "^2.3.0", "postcss": "^8.4.32", "tailwindcss": "^3.3.6", "vite": "^4.5.1", "vite-svg-loader": "^5.1.0", - "vitest": "1.0" - }, - "eslintConfig": { - "root": true, - "env": { - "node": true - }, - "extends": [ - "plugin:vue/vue3-essential", - "eslint:recommended" - ], - "parserOptions": { - "parser": "@babel/eslint-parser", - "requireConfigFile": false - }, - "rules": {} + "vitest": "^1.2.1" }, "browserslist": [ "> 1%", diff --git a/client/public/favicon.png b/client/public/assets/favicon.png similarity index 100% rename from client/public/favicon.png rename to client/public/assets/favicon.png diff --git a/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap b/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap index 08d0b771..743b0d94 100644 --- a/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap +++ b/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap @@ -27,7 +27,7 @@ exports[`SearchResultCard with all data > search result card exists with full da -->

Time range

-

12/31/2016–12/31/2017

+

2016–2017

Formats available

  • [
  • @@ -81,7 +81,7 @@ exports[`SearchResultCard with coverage end but not start > search result card e -->

    Time range

    -

    Unknown start–12/31/2016

    +

    Unknown start–2016

    Formats available

    Unknown

    @@ -119,7 +119,7 @@ exports[`SearchResultCard with coverage start but not end > search result card e
    -->

    Time range

    -

    12/31/2016–Unknown end

    +

    2016–Unknown end

    Formats available

    Unknown

    diff --git a/client/src/components/__tests__/searchResultCard.test.js b/client/src/components/__tests__/searchResultCard.test.js index fbf9eacc..51ccfef2 100644 --- a/client/src/components/__tests__/searchResultCard.test.js +++ b/client/src/components/__tests__/searchResultCard.test.js @@ -1,7 +1,7 @@ -import SearchResultCard from "../SearchResultCard.vue"; -import { mount } from "@vue/test-utils"; -import { describe, it, expect, beforeEach, vi } from "vitest"; -import { nextTick } from "vue"; +import SearchResultCard from '../SearchResultCard.vue'; +import { mount } from '@vue/test-utils'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { nextTick } from 'vue'; let wrapper; @@ -11,21 +11,21 @@ const $routerMock = { push, }; -describe("SearchResultCard with all data", () => { +describe('SearchResultCard with all data', () => { const dataSource = { - agency_name: "Cicero Police Department - IN", + agency_name: 'Cicero Police Department - IN', agency_supplied: false, - airtable_uid: "rec7edsmcuVQwMMXG", - coverage_end: "2018-01-01", - coverage_start: "2017-01-01", - data_source_name: "Calls for Service for Cicero Police Department - IN", - municipality: "Bridgeview", - name: "Cicero Police Department - IN", + airtable_uid: 'rec7edsmcuVQwMMXG', + coverage_end: '2018-01-01', + coverage_start: '2017-01-01', + data_source_name: 'Calls for Service for Cicero Police Department - IN', + municipality: 'Bridgeview', + name: 'Cicero Police Department - IN', record_format: "['json', 'pdf']", - record_type: "Calls for Service", + record_type: 'Calls for Service', source_url: - "https://cityprotect.com/agency/cc0d9a3d-50b2-4424-ae25-5699ba6eaff9", - state_iso: "IN", + 'https://cityprotect.com/agency/cc0d9a3d-50b2-4424-ae25-5699ba6eaff9', + state_iso: 'IN', }; beforeEach(() => { @@ -41,26 +41,26 @@ describe("SearchResultCard with all data", () => { vi.unstubAllGlobals(); }); - it("search result card exists with full data", () => { + it('search result card exists with full data', () => { expect(wrapper.find('[data-test="search-result-card"]').exists()).toBe( true, ); expect(wrapper.html()).toMatchSnapshot(); }); - it("search result card contains a title", () => { + it('search result card contains a title', () => { expect(wrapper.get('[data-test="search-result-title"]').text()).toBe( dataSource.data_source_name, ); }); - it("search result card contains an agency section", () => { + it('search result card contains an agency section', () => { expect(wrapper.find('[data-test="search-result-agency"]').exists()).toBe( true, ); }); - it("search result card contains correct agency name", () => { + it('search result card contains correct agency name', () => { expect(wrapper.get('[data-test="search-result-agency-known"]').text()).toBe( dataSource.agency_name, ); @@ -78,25 +78,25 @@ describe("SearchResultCard with all data", () => { ).toBe(`${dataSource.municipality}, ${dataSource.state_iso}`); }); */ - it("search result card contains a record section", () => { + it('search result card contains a record section', () => { expect( wrapper.find('[data-test="search-result-record-label"]').exists(), ).toBe(true); }); - it("search result card contains correct record type", () => { + it('search result card contains correct record type', () => { expect(wrapper.get('[data-test="search-result-record-type"]').text()).toBe( dataSource.record_type, ); }); - it("search result card contains a coverage date section", () => { + it('search result card contains a coverage date section', () => { expect( wrapper.find('[data-test="search-result-label-coverage"]').exists(), ).toBe(true); }); - it("search result card contains correct coverage start and end date", () => { + it('search result card contains correct coverage start and end date', () => { expect( wrapper.get('[data-test="search-result-coverage-start-end"]').text(), ).toBe( @@ -106,20 +106,20 @@ describe("SearchResultCard with all data", () => { ); }); - it("search result card contains a record formats section", () => { + it('search result card contains a record formats section', () => { expect( wrapper.find('[data-test="search-result-label-formats"]').exists(), ).toBe(true); }); - it("search result card contains a record formats div for each format", () => { + it('search result card contains a record formats div for each format', () => { expect(wrapper.find('[data-test="search-result-formats"]').exists()).toBe( true, ); }); - it("Visit source button exists and calls window navigation with correct value on click", async () => { - const spy = vi.spyOn(window, "open"); + it('Visit source button exists and calls window navigation with correct value on click', async () => { + const spy = vi.spyOn(window, 'open'); const button = wrapper.find( '[data-test="search-result-visit-source-button"]', @@ -127,22 +127,22 @@ describe("SearchResultCard with all data", () => { expect(button.exists()).toBe(true); - await button.trigger("click"); + await button.trigger('click'); await nextTick(); expect(spy).toHaveBeenCalledOnce(); - expect(spy).toHaveBeenCalledWith(dataSource.source_url, "_blank"); + expect(spy).toHaveBeenCalledWith(dataSource.source_url, '_blank'); }); - it("View details button exists and calls router.push with correct value on click", async () => { + it('View details button exists and calls router.push with correct value on click', async () => { const button = wrapper.find( '[data-test="search-result-source-details-button"]', ); expect(button.exists()).toBe(true); - await button.trigger("click"); + await button.trigger('click'); await nextTick(); @@ -153,14 +153,14 @@ describe("SearchResultCard with all data", () => { }); }); -describe("SearchResultCard with missing data", () => { +describe('SearchResultCard with missing data', () => { const dataSource = { agency_name: null, agency_supplied: false, - airtable_uid: "rec7edsmcuVQwMMXG", + airtable_uid: 'rec7edsmcuVQwMMXG', coverage_end: null, coverage_start: null, - data_source_name: "Calls for Service for Cicero Police Department - IN", + data_source_name: 'Calls for Service for Cicero Police Department - IN', municipality: null, name: null, record_format: null, @@ -175,14 +175,14 @@ describe("SearchResultCard with missing data", () => { }); }); - it("search result card exists with missing data", () => { + it('search result card exists with missing data', () => { expect(wrapper.find('[data-test="search-result-card"]').exists()).toBe( true, ); expect(wrapper.html()).toMatchSnapshot(); }); - it("search result card contains unknown for agency name", () => { + it('search result card contains unknown for agency name', () => { expect( wrapper.find('[data-test="search-result-agency-unknown"]').exists(), ).toBe(true); @@ -194,19 +194,19 @@ describe("SearchResultCard with missing data", () => { // ).toBe(true); // }); - it("search result card contains unknown for record type", () => { + it('search result card contains unknown for record type', () => { expect( wrapper.find('[data-test="search-result-record-type-unknown"]').exists(), ).toBe(true); }); - it("search result card contains unknown for coverage", () => { + it('search result card contains unknown for coverage', () => { expect( wrapper.find('[data-test="search-result-coverage-unknown"]').exists(), ).toBe(true); }); - it("search result card contains unknown for record formats", () => { + it('search result card contains unknown for record formats', () => { expect( wrapper.find('[data-test="search-result-format-unknown"]').exists(), ).toBe(true); @@ -247,9 +247,9 @@ describe("SearchResultCard with missing data", () => { // }); // }); -describe("SearchResultCard with coverage start but not end", () => { +describe('SearchResultCard with coverage start but not end', () => { const dataSource = { - coverage_start: "2017-01-01", + coverage_start: '2017-01-01', coverage_end: null, }; @@ -259,24 +259,24 @@ describe("SearchResultCard with coverage start but not end", () => { }); }); - it("search result card exists with coverage start but not end", () => { + it('search result card exists with coverage start but not end', () => { expect(wrapper.find('[data-test="search-result-card"]').exists()).toBe( true, ); expect(wrapper.html()).toMatchSnapshot(); }); - it("search result card has only coverage start date", () => { + it('search result card has only coverage start date', () => { expect( wrapper.get('[data-test="search-result-coverage-start"]').text(), ).toContain(wrapper.vm.formatDate(dataSource.coverage_start)); }); }); -describe("SearchResultCard with coverage end but not start", () => { +describe('SearchResultCard with coverage end but not start', () => { const dataSource = { coverage_start: null, - coverage_end: "2017-01-01", + coverage_end: '2017-01-01', }; beforeEach(() => { @@ -285,14 +285,14 @@ describe("SearchResultCard with coverage end but not start", () => { }); }); - it("search result card exists with coverage end but not start", () => { + it('search result card exists with coverage end but not start', () => { expect(wrapper.find('[data-test="search-result-card"]').exists()).toBe( true, ); expect(wrapper.html()).toMatchSnapshot(); }); - it("search result card has only coverage end date", () => { + it('search result card has only coverage end date', () => { expect( wrapper.get('[data-test="search-result-coverage-end"]').text(), ).toContain(wrapper.vm.formatDate(dataSource.coverage_end)); diff --git a/client/src/main.css b/client/src/main.css index bd6213e1..814ed8f0 100644 --- a/client/src/main.css +++ b/client/src/main.css @@ -1,3 +1,11 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer utilities { + /* Creating utility class in order to apply these dynamically */ + /* TODO: move to design-system, so it can be used elsewhere */ + .truncate-text { + @apply overflow-hidden whitespace-nowrap overflow-ellipsis + } +} \ No newline at end of file diff --git a/client/src/main.js b/client/src/main.js index 3eb14b2f..affa8e3d 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -1,16 +1,16 @@ import './main.css'; -import { createApp } from "vue"; -import App from "./App.vue"; -import router from "./router"; +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router'; -import "pdap-design-system/styles"; -import { FlexContainer } from "pdap-design-system"; +import 'pdap-design-system/styles'; +import { FlexContainer } from 'pdap-design-system'; const app = createApp(App); app.use(router); // Register 'FlexContainer' so it can be passed as a grid item -app.component("FlexContainer", FlexContainer); +app.component('FlexContainer', FlexContainer); -app.mount("#app"); +app.mount('#app'); diff --git a/client/src/pages/DataSourceStaticView.vue b/client/src/pages/DataSourceStaticView.vue index 7801b551..a38118bf 100644 --- a/client/src/pages/DataSourceStaticView.vue +++ b/client/src/pages/DataSourceStaticView.vue @@ -1,22 +1,23 @@ + + diff --git a/client/src/pages/QuickSearchPage.vue b/client/src/pages/QuickSearchPage.vue index 9329de07..eee10d93 100644 --- a/client/src/pages/QuickSearchPage.vue +++ b/client/src/pages/QuickSearchPage.vue @@ -5,10 +5,10 @@ From fce00c08ffa722804ead05f42c9d883b4181f945 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 12 Feb 2024 13:16:04 -0500 Subject: [PATCH 11/73] feat(pages): add shell of password page --- client/src/pages/ChangePassword.vue | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 client/src/pages/ChangePassword.vue diff --git a/client/src/pages/ChangePassword.vue b/client/src/pages/ChangePassword.vue new file mode 100644 index 00000000..1d13abb1 --- /dev/null +++ b/client/src/pages/ChangePassword.vue @@ -0,0 +1,6 @@ + + From a813bbb4f44529c311eb98b0822c316e32d0135d Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 12 Feb 2024 13:16:18 -0500 Subject: [PATCH 12/73] feat(router): add new pages to router --- client/src/router.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/src/router.js b/client/src/router.js index d4487660..d8322b30 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -2,6 +2,8 @@ import { createWebHistory, createRouter } from 'vue-router'; import QuickSearchPage from '../src/pages/QuickSearchPage.vue'; import SearchResultPage from '../src/pages/SearchResultPage.vue'; import DataSourceStaticView from '../src/pages/DataSourceStaticView.vue'; +import ChangePassword from './pages/ChangePassword.vue'; +import LogIn from './pages/LogIn.vue'; const routes = [ { path: '/', component: QuickSearchPage, name: 'QuickSearchPage' }, @@ -15,6 +17,16 @@ const routes = [ component: DataSourceStaticView, name: 'DataSourceStaticView', }, + { + path: '/login', + component: LogIn, + name: 'LogIn', + }, + { + path: '/change-password', + component: ChangePassword, + name: 'ChangePassword', + }, ]; const router = createRouter({ From dda07db415d1cb6b74f75bfdc46750f256734f2c Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Thu, 15 Feb 2024 14:07:38 -0500 Subject: [PATCH 13/73] reset password, role check for edit permissions --- app.py | 12 ++++++++ app_test.py | 47 ++++++++++++++++++++++++++++++- middleware/data_source_queries.py | 2 +- middleware/security.py | 31 +++++++++++++------- middleware/user_queries.py | 20 +++++++++++-- regular_api_checks.py | 21 ++++++++++++-- resources/ApiKey.py | 2 +- resources/RequestResetPassword.py | 46 ++++++++++++++++++++++++++++++ resources/ResetPassword.py | 34 ++++++++++++++++++++++ resources/User.py | 15 ++++++---- 10 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 resources/RequestResetPassword.py create mode 100644 resources/ResetPassword.py diff --git a/app.py b/app.py index a5617954..0db30f35 100644 --- a/app.py +++ b/app.py @@ -3,6 +3,8 @@ from flask_cors import CORS from resources.User import User from resources.ApiKey import ApiKey +from resources.RequestResetPassword import RequestResetPassword +from resources.ResetPassword import ResetPassword from resources.QuickSearch import QuickSearch from resources.DataSources import DataSources from resources.DataSources import DataSourceById @@ -25,6 +27,16 @@ "/api_key", resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, ) +api.add_resource( + RequestResetPassword, + "/request-reset-password", + resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, +) +api.add_resource( + ResetPassword, + "/reset-password/", + resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, +) api.add_resource( QuickSearch, "/quick-search//", diff --git a/app_test.py b/app_test.py index 3d5f8e0d..4fc4b50e 100644 --- a/app_test.py +++ b/app_test.py @@ -14,7 +14,11 @@ data_source_by_id_results, DATA_SOURCES_APPROVED_COLUMNS, ) -from middleware.user_queries import user_get_results, user_post_results +from middleware.user_queries import ( + user_get_results, + user_post_results, + user_check_email, +) from middleware.archives_queries import ( archives_get_results, archives_get_query, @@ -22,6 +26,11 @@ archives_put_last_cached_results, ARCHIVES_GET_COLUMNS, ) +from middleware.reset_token_queries import ( + check_reset_token, + add_reset_token, + delete_reset_token, +) from app_test_data import ( DATA_SOURCES_ROWS, DATA_SOURCE_QUERY_RESULTS, @@ -143,6 +152,42 @@ def test_user_post_query(session): assert email_check == "unit_test" +def test_user_check_email(session): + curs = session.cursor() + user_data = user_check_email(curs, "test") + + assert user_data["id"] + + +def test_check_reset_token(session): + curs = session.cursor() + reset_token = check_reset_token(curs, "test") + + assert reset_token["id"] + + +def test_add_reset_token(session): + curs = session.cursor() + add_reset_token(curs, "unit_test", "unit_test") + + email_check = curs.execute( + f"SELECT email FROM reset_tokens WHERE email = 'unit_test'" + ).fetchone()[0] + + assert email_check == "unit_test" + + +def test_delete_reset_token(session): + curs = session.cursor() + delete_reset_token(curs, "test", "test") + + email_check = curs.execute( + f"SELECT email FROM reset_tokens WHERE email = 'test'" + ).fetchone() + + assert not email_check + + def test_archives_get_results(session): response = archives_get_results(conn=session) diff --git a/middleware/data_source_queries.py b/middleware/data_source_queries.py index 3738bde7..40d75765 100644 --- a/middleware/data_source_queries.py +++ b/middleware/data_source_queries.py @@ -164,7 +164,7 @@ def data_sources_results(conn): def data_sources_query(conn={}, test_query_results=[]): - results = data_sources_results(conn, "", "") if conn else test_query_results + results = data_sources_results(conn) if conn else test_query_results data_source_output_columns = DATA_SOURCES_APPROVED_COLUMNS + ["agency_name"] diff --git a/middleware/security.py b/middleware/security.py index 6734c90b..9a01fbea 100644 --- a/middleware/security.py +++ b/middleware/security.py @@ -6,14 +6,20 @@ import os -def is_valid(api_key): +def is_valid(api_key, endpoint, method): + """ + Get the user data that matches the API key from the request + """ psycopg2_connection = initialize_psycopg2_connection() - # Get the user data that matches the API key from the request cursor = psycopg2_connection.cursor() - cursor.execute(f"select id, api_key from users where api_key = '{api_key}'") + cursor.execute(f"select id, api_key, role from users where api_key = '{api_key}'") results = cursor.fetchall() user_data = {} - if not results: + if endpoint in ("datasources", "datasourcebyid") and method in ("PUT", "POST"): + if results[0][2] != "admin": + return False + + elif not results: cursor.execute( f"delete from access_tokens where expiration_date < '{dt.now()}'" ) @@ -25,16 +31,21 @@ def is_valid(api_key): return False user_data = dict(zip(("id", "api_key"), results[0])) - # Compare the API key in the user table to the API in the request header and proceed through the protected route if it's valid. Otherwise, compare_digest will return False and api_required will send an error message to provide a valid API key + # Compare the API key in the user table to the API in the request header and proceed + # through the protected route if it's valid. Otherwise, compare_digest will return False + # and api_required will send an error message to provide a valid API key if compare_digest(user_data.get("api_key"), api_key): return True -# The api_required decorator can be added to protect a route so that only authenticated users can access the information -# To protect a route with this decorator, add @api_required on the line above a given route -# The request header for a protected route must include an "Authorization" key with the value formatted as "Bearer [api_key]" -# A user can get an API key by signing up and logging in (see User.py) def api_required(func): + """ + The api_required decorator can be added to protect a route so that only authenticated users can access the information + To protect a route with this decorator, add @api_required on the line above a given route + The request header for a protected route must include an "Authorization" key with the value formatted as "Bearer [api_key]" + A user can get an API key by signing up and logging in (see User.py) + """ + @functools.wraps(func) def decorator(*args, **kwargs): api_key = None @@ -53,7 +64,7 @@ def decorator(*args, **kwargs): "message": "Please provide an 'Authorization' key in the request header" }, 400 # Check if API key is correct and valid - if is_valid(api_key): + if is_valid(api_key, request.endpoint, request.method): return func(*args, **kwargs) else: return {"message": "The provided API key is not valid"}, 403 diff --git a/middleware/user_queries.py b/middleware/user_queries.py index f42a686b..841a441e 100644 --- a/middleware/user_queries.py +++ b/middleware/user_queries.py @@ -1,11 +1,27 @@ from werkzeug.security import generate_password_hash +def user_check_email(cursor, email): + cursor.execute(f"select id from users where email = '{email}'") + results = cursor.fetchall() + if len(results) > 0: + user_data = {"id": results[0][0]} + return user_data + else: + return {"error": "no match"} + + def user_get_results(cursor, email): - cursor.execute(f"select id, password_digest from users where email = '{email}'") + cursor.execute( + f"select id, password_digest, api_key from users where email = '{email}'" + ) results = cursor.fetchall() if len(results) > 0: - user_data = {"id": results[0][0], "password_digest": results[0][1]} + user_data = { + "id": results[0][0], + "password_digest": results[0][1], + "api_key": results[0][2], + } return user_data else: return {"error": "no match"} diff --git a/regular_api_checks.py b/regular_api_checks.py index 9bc289ff..976549a0 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -150,7 +150,7 @@ def test_get_user(): json={"email": "test2", "password": "test"}, ) - return response.json()["data"] == "Successfully logged in" + return response.json()["message"] == "Successfully logged in" def test_put_user(): @@ -160,7 +160,23 @@ def test_put_user(): json={"email": "test2", "password": "test"}, ) - return response.json()["data"] == "Successfully updated password" + return response.json()["message"] == "Successfully updated password" + + +# reset-password +def test_request_reset_password(): + reset_token = requests.get( + "https://data-sources.pdap.io/api/request-reset-password", + headers=HEADERS, + json={"email": "test"}, + ) + + response = requests.get( + f"https://data-sources.pdap.io/api/reset-password{reset_token['token']}", + headers=HEADERS, + ) + + return response.json()["message"] == "Successfully requested password reset" # api-key @@ -257,6 +273,7 @@ def main(): "test_search_tokens_quick_search_complaints_allegheny_results", "test_get_user", "test_put_user", + "test_request_reset_password", "test_get_api_key", "test_get_archives", "test_put_archives", diff --git a/resources/ApiKey.py b/resources/ApiKey.py index 7d2d972a..d0609e21 100644 --- a/resources/ApiKey.py +++ b/resources/ApiKey.py @@ -33,4 +33,4 @@ def get(self): except Exception as e: self.psycopg2_connection.rollback() print(str(e)) - return {"error": str(e)} + return {"message": str(e)} diff --git a/resources/RequestResetPassword.py b/resources/RequestResetPassword.py new file mode 100644 index 00000000..0586f4e0 --- /dev/null +++ b/resources/RequestResetPassword.py @@ -0,0 +1,46 @@ +from werkzeug.security import generate_password_hash, check_password_hash +from flask_restful import Resource +from flask import request +from middleware.user_queries import user_check_email +from middleware.reset_token_queries import add_reset_token +import os +import uuid +import requests + + +class RequestResetPassword(Resource): + def __init__(self, **kwargs): + self.psycopg2_connection = kwargs["psycopg2_connection"] + + def get(self): + try: + data = request.get_json() + email = data.get("email") + cursor = self.psycopg2_connection.cursor() + user_data = user_check_email(cursor, email) + id = user_data["id"] + token = uuid.uuid4().hex + add_reset_token(cursor, email, token) + self.psycopg2_connection.commit() + + body = f"To reset your password, click the following link: {os.getenv('VITE_VUE_APP_BASE_URL')}/reset-password/{token}" + r = requests.post( + "https://api.mailgun.net/v3/mail.pdap.io/messages", + auth=("api", os.getenv("MAILGUN_KEY")), + data={ + "from": "mail@pdap.io", + "to": [email], + "subject": "PDAP Data Sources Reset Password", + "text": body, + }, + ) + + return { + "message": "An email has been sent to your email address with a link to reset your password.", + "token": token, + } + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"error": str(e)}, 500 diff --git a/resources/ResetPassword.py b/resources/ResetPassword.py new file mode 100644 index 00000000..5d4b0b26 --- /dev/null +++ b/resources/ResetPassword.py @@ -0,0 +1,34 @@ +from werkzeug.security import generate_password_hash +from flask_restful import Resource +from flask import request +from middleware.reset_token_queries import ( + check_reset_token, + add_reset_token, + delete_reset_token, +) +from datetime import datetime as dt + + +class ResetPassword(Resource): + def __init__(self, **kwargs): + self.psycopg2_connection = kwargs["psycopg2_connection"] + + def get(self, token): + try: + cursor = self.psycopg2_connection.cursor() + token_data = check_reset_token(cursor, token) + if "create_date" not in token_data: + return {"message": "The submitted token is invalid"}, 400 + + token_create_date = token_data["create_date"] + token_expired = (dt.utcnow() - token_create_date).total_seconds() > 300 + delete_reset_token(cursor, token_data["email"], token) + if token_expired: + return {"message": "The submitted token is invalid"}, 400 + + return {"message": "The submitted token is valid"} + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"message": str(e)}, 500 diff --git a/resources/User.py b/resources/User.py index c9d172ac..186794e2 100644 --- a/resources/User.py +++ b/resources/User.py @@ -22,12 +22,15 @@ def get(self): user_data = user_get_results(cursor, email) if check_password_hash(user_data["password_digest"], password): - return {"data": "Successfully logged in"} + return { + "message": "Successfully logged in", + "data": user_data["api_key"], + } except Exception as e: self.psycopg2_connection.rollback() print(str(e)) - return {"error": str(e)} + return {"message": str(e)}, 500 def post(self): """ @@ -42,12 +45,12 @@ def post(self): user_post_results(cursor, email, password) self.psycopg2_connection.commit() - return {"data": "Successfully added user"} + return {"message": "Successfully added user"} except Exception as e: self.psycopg2_connection.rollback() print(str(e)) - return {"error": e} + return {"message": e}, 500 # Endpoint for updating a user's password def put(self): @@ -61,9 +64,9 @@ def put(self): f"update users set password_digest = '{password_digest}' where email = '{email}'" ) self.psycopg2_connection.commit() - return {"data": "Successfully updated password"} + return {"message": "Successfully updated password"} except Exception as e: self.psycopg2_connection.rollback() print(str(e)) - return {"error": e} + return {"message": e}, 500 From 75f55317a86932a62b20cb5ad16a95d353ea59dd Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Thu, 15 Feb 2024 14:10:43 -0500 Subject: [PATCH 14/73] missing file --- middleware/reset_token_queries.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 middleware/reset_token_queries.py diff --git a/middleware/reset_token_queries.py b/middleware/reset_token_queries.py new file mode 100644 index 00000000..dc4d934e --- /dev/null +++ b/middleware/reset_token_queries.py @@ -0,0 +1,30 @@ +def check_reset_token(cursor, token): + cursor.execute( + f"select id, create_date, email from reset_tokens where token = '{token}'" + ) + results = cursor.fetchall() + if len(results) > 0: + user_data = { + "id": results[0][0], + "create_date": results[0][1], + "email": results[0][2], + } + return user_data + else: + return {"error": "no match"} + + +def add_reset_token(cursor, email, token): + cursor.execute( + f"insert into reset_tokens (email, token) values ('{email}', '{token}')" + ) + + return + + +def delete_reset_token(cursor, email, token): + cursor.execute( + f"delete from reset_tokens where email = '{email}' and token = '{token}'" + ) + + return From c11d49f935315c078016b8c7cb3d91cf4a0a00ca Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Fri, 16 Feb 2024 09:01:12 -0500 Subject: [PATCH 15/73] fixed tests --- app_test.py | 2 ++ do_db_ddl_clean.sql | 10 +++++++++- resources/DataSources.py | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app_test.py b/app_test.py index 4fc4b50e..5f20ae0c 100644 --- a/app_test.py +++ b/app_test.py @@ -155,6 +155,7 @@ def test_user_post_query(session): def test_user_check_email(session): curs = session.cursor() user_data = user_check_email(curs, "test") + print(user_data) assert user_data["id"] @@ -162,6 +163,7 @@ def test_user_check_email(session): def test_check_reset_token(session): curs = session.cursor() reset_token = check_reset_token(curs, "test") + print(reset_token) assert reset_token["id"] diff --git a/do_db_ddl_clean.sql b/do_db_ddl_clean.sql index 99e0e5e1..acc0ebd8 100644 --- a/do_db_ddl_clean.sql +++ b/do_db_ddl_clean.sql @@ -143,6 +143,13 @@ CREATE TABLE if not exists users ( api_key character varying ); +CREATE TABLE if not exists reset_tokens ( + id serial primary key, + email text NOT NULL, + token text varying NOT NULL, + create_date timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE if not exists volunteers ( discord text, email text, @@ -164,4 +171,5 @@ INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_u INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (4, 'rec8gO2K86yk9mQIU', 'recRvBpZqXM8mjddz'); INSERT INTO state_names VALUES (1, 'IL', 'Illinois'); INSERT INTO state_names VALUES (2, 'PA', 'Pennsylvania'); -INSERT INTO users (email, password_digest) VALUES ("test", "test"); \ No newline at end of file +INSERT INTO users (id, email, password_digest) VALUES (1, "test", "test"); +INSERT INTO reset_tokens (id, email, token) VALUES (1, "test", "test"); diff --git a/resources/DataSources.py b/resources/DataSources.py index 7d9e2f78..eacf739c 100644 --- a/resources/DataSources.py +++ b/resources/DataSources.py @@ -60,8 +60,6 @@ def put(self, data_source_id): WHERE airtable_uid = '{data_source_id}' """ - print(sql_query) - cursor.execute(sql_query) self.psycopg2_connection.commit() return {"message": "Data source updated successfully."} From 895841b2fbec73c2febf34adf32221673b0ea525 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Fri, 16 Feb 2024 14:18:10 -0500 Subject: [PATCH 16/73] fixed tests --- regular_api_checks.py | 85 ++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/regular_api_checks.py b/regular_api_checks.py index 976549a0..71e88c7e 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -3,14 +3,15 @@ import json import requests -api_key = os.getenv("VUE_APP_PDAP_API_KEY") -HEADERS = {"Authorization": f"Bearer {api_key}"} +API_KEY = os.getenv("VUE_APP_PDAP_API_KEY") +BASE_URL = os.getenv("VITE_VUE_APP_BASE_URL") +HEADERS = {"Authorization": f"Bearer {API_KEY}"} # quick-search def test_quicksearch_officer_involved_shootings_philadelphia_results(): response = requests.get( - "https://data-sources.pdap.io/api/quick-search/Officer Involved Shootings/philadelphia", + f"{BASE_URL}/quick-search/Officer Involved Shootings/philadelphia", headers=HEADERS, ) @@ -19,7 +20,7 @@ def test_quicksearch_officer_involved_shootings_philadelphia_results(): def test_quicksearch_officer_involved_shootings_lowercase_philadelphia_results(): response = requests.get( - "https://data-sources.pdap.io/api/quick-search/officer involved shootings/Philadelphia", + f"{BASE_URL}/quick-search/officer involved shootings/Philadelphia", headers=HEADERS, ) @@ -28,7 +29,7 @@ def test_quicksearch_officer_involved_shootings_lowercase_philadelphia_results() def test_quicksearch_officer_involved_shootings_philadelphia_county_results(): response = requests.get( - "https://data-sources.pdap.io/api/quick-search/Officer Involved Shootings/philadelphia county", + f"{BASE_URL}/quick-search/Officer Involved Shootings/philadelphia county", headers=HEADERS, ) @@ -36,24 +37,20 @@ def test_quicksearch_officer_involved_shootings_philadelphia_county_results(): def test_quicksearch_all_allgeheny_results(): - response = requests.get( - "https://data-sources.pdap.io/api/quick-search/all/allegheny", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/quick-search/all/allegheny", headers=HEADERS) return len(response.json()["data"]) > 0 def test_quicksearch_complaints_all_results(): - response = requests.get( - "https://data-sources.pdap.io/api/quick-search/complaints/all", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/quick-search/complaints/all", headers=HEADERS) return len(response.json()["data"]) > 0 def test_quicksearch_media_bulletin_pennsylvania_results(): response = requests.get( - "https://data-sources.pdap.io/api/quick-search/media bulletin/pennsylvania", + f"{BASE_URL}/quick-search/media bulletin/pennsylvania", headers=HEADERS, ) @@ -63,24 +60,24 @@ def test_quicksearch_media_bulletin_pennsylvania_results(): # data-sources def test_data_source_by_id(): response = requests.get( - "https://data-sources.pdap.io/api/data-sources-by-id/reczwxaH31Wf9gRjS", + f"{BASE_URL}/data-sources-by-id/reczwxaH31Wf9gRjS", headers=HEADERS, ) - return response.json()["data_source_id"] == "reczwxaH31Wf9gRjS" + return len(response.json()["data"]) > 0 def test_data_sources(): - response = requests.get( - "https://data-sources.pdap.io/api/data-sources", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/data-sources", headers=HEADERS) return len(response.json()["data"]) > 0 def test_create_data_source(): response = requests.post( - "/data-sources", headers=HEADERS, json={"name": "test", "record_type": "test"} + f"{BASE_URL}/data-sources", + headers=HEADERS, + json={"name": "test", "record_type": "test"}, ) assert response.json() == True @@ -88,18 +85,16 @@ def test_create_data_source(): def test_update_data_source(): response = requests.put( - "/data-sources-by-id/45a4cd5d-26da-473a-a98e-a39fbcf4a96c", + f"{BASE_URL}/data-sources-by-id/45a4cd5d-26da-473a-a98e-a39fbcf4a96c", headers=HEADERS, json={"description": "test"}, ) - assert response.json()["status"] == "success" + assert response.json()["message"] == "Data source updated successfully." def test_data_sources_approved(): - response = requests.get( - "https://data-sources.pdap.io/api/data-sources", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/data-sources", headers=HEADERS) unapproved_url = "https://joinstatepolice.ny.gov/15-mile-run" return ( @@ -110,7 +105,7 @@ def test_data_sources_approved(): def test_data_source_by_id_approved(): response = requests.get( - "https://data-sources.pdap.io/api/data-sources-by-id/rec013MFNfBnrTpZj", + f"{BASE_URL}/data-sources-by-id/rec013MFNfBnrTpZj", headers=HEADERS, ) @@ -119,16 +114,14 @@ def test_data_source_by_id_approved(): # search-tokens def test_search_tokens_data_sources(): - response = requests.get( - "https://data-sources.pdap.io/api/search-tokens?endpoint=data-sources" - ) + response = requests.get(f"{BASE_URL}/search-tokens?endpoint=data-sources") return len(response.json()["data"]) > 0 def test_search_tokens_data_source_by_id(): response = requests.get( - "https://data-sources.pdap.io/api/search-tokens?endpoint=data-sources-by-id&arg1=reczwxaH31Wf9gRjS" + f"{BASE_URL}/search-tokens?endpoint=data-sources-by-id&arg1=reczwxaH31Wf9gRjS" ) return response.json()["data_source_id"] == "reczwxaH31Wf9gRjS" @@ -136,7 +129,7 @@ def test_search_tokens_data_source_by_id(): def test_search_tokens_quick_search_complaints_allegheny_results(): response = requests.get( - "https://data-sources.pdap.io/api/search-tokens?endpoint=quick-search&arg1=complaints&arg2=allegheny" + f"{BASE_URL}/search-tokens?endpoint=quick-search&arg1=complaints&arg2=allegheny" ) return len(response.json()["data"]) > 0 @@ -145,7 +138,7 @@ def test_search_tokens_quick_search_complaints_allegheny_results(): # user def test_get_user(): response = requests.get( - "https://data-sources.pdap.io/api/user", + f"{BASE_URL}/user", headers=HEADERS, json={"email": "test2", "password": "test"}, ) @@ -154,8 +147,8 @@ def test_get_user(): def test_put_user(): - response = requests.get( - "https://data-sources.pdap.io/api/user", + response = requests.put( + f"{BASE_URL}/user", headers=HEADERS, json={"email": "test2", "password": "test"}, ) @@ -166,23 +159,23 @@ def test_put_user(): # reset-password def test_request_reset_password(): reset_token = requests.get( - "https://data-sources.pdap.io/api/request-reset-password", + f"{BASE_URL}/request-reset-password", headers=HEADERS, json={"email": "test"}, ) response = requests.get( - f"https://data-sources.pdap.io/api/reset-password{reset_token['token']}", + f"{BASE_URL}/reset-password/{reset_token.json()['token']}", headers=HEADERS, ) - return response.json()["message"] == "Successfully requested password reset" + return response.json()["message"] == "The submitted token is valid" # api-key def test_get_api_key(): response = requests.get( - "https://data-sources.pdap.io/api/api_key", + f"{BASE_URL}/api_key", headers=HEADERS, json={"email": "test2", "password": "test"}, ) @@ -192,9 +185,7 @@ def test_get_api_key(): # archives def test_get_archives(): - response = requests.get( - "https://data-sources.pdap.io/api/archives", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/archives", headers=HEADERS) return len(response.json()[0]) > 0 @@ -203,7 +194,7 @@ def test_put_archives(): current_datetime = datetime.datetime.now() datetime_string = current_datetime.strftime("%Y-%m-%d %H:%M:%S") response = requests.put( - "https://data-sources.pdap.io/api/archives", + f"{BASE_URL}/archives", headers=HEADERS, json=json.dumps( { @@ -221,7 +212,7 @@ def test_put_archives_brokenasof(): current_datetime = datetime.datetime.now() datetime_string = current_datetime.strftime("%Y-%m-%d") response = requests.put( - "https://data-sources.pdap.io/api/archives", + f"{BASE_URL}/archives", headers=HEADERS, json=json.dumps( { @@ -237,20 +228,14 @@ def test_put_archives_brokenasof(): # agencies def test_agencies(): - response = requests.get( - "https://data-sources.pdap.io/api/agencies/1", headers=HEADERS - ) + response = requests.get(f"{BASE_URL}/agencies/1", headers=HEADERS) return len(response.json()["data"]) > 0 def test_agencies_pagination(): - response1 = requests.get( - "https://data-sources.pdap.io/api/agencies/1", headers=HEADERS - ) - response2 = requests.get( - "https://data-sources.pdap.io/api/agencies/2", headers=HEADERS - ) + response1 = requests.get(f"{BASE_URL}/agencies/1", headers=HEADERS) + response2 = requests.get(f"{BASE_URL}/agencies/2", headers=HEADERS) return response1 != response2 From 20d8cdaa3ac9e161a6e5ab4af68e6dcfc9879997 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Fri, 16 Feb 2024 14:58:48 -0500 Subject: [PATCH 17/73] don't insert search log on test --- middleware/quick_search_query.py | 6 ++++-- regular_api_checks.py | 16 ++++++++++++++-- resources/DataSources.py | 5 ++++- resources/QuickSearch.py | 16 +++++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/middleware/quick_search_query.py b/middleware/quick_search_query.py index 26d42f96..5e29d20f 100644 --- a/middleware/quick_search_query.py +++ b/middleware/quick_search_query.py @@ -80,7 +80,9 @@ def spacy_search_query(cursor, search, location): return results -def quick_search_query(search="", location="", test_query_results=[], conn={}): +def quick_search_query( + search="", location="", test_query_results=[], conn={}, test=False +): data_sources = {"count": 0, "data": []} if type(conn) == dict and "data" in conn: return data_sources @@ -122,7 +124,7 @@ def quick_search_query(search="", location="", test_query_results=[], conn={}): "data": data_source_matches_converted, } - if not test_query_results: + if not test_query_results and not test: current_datetime = datetime.datetime.now() datetime_string = current_datetime.strftime("%Y-%m-%d %H:%M:%S") diff --git a/regular_api_checks.py b/regular_api_checks.py index 71e88c7e..59f8a54e 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -13,6 +13,7 @@ def test_quicksearch_officer_involved_shootings_philadelphia_results(): response = requests.get( f"{BASE_URL}/quick-search/Officer Involved Shootings/philadelphia", headers=HEADERS, + json={"test_flag": True}, ) return len(response.json()["data"]) > 0 @@ -22,6 +23,7 @@ def test_quicksearch_officer_involved_shootings_lowercase_philadelphia_results() response = requests.get( f"{BASE_URL}/quick-search/officer involved shootings/Philadelphia", headers=HEADERS, + json={"test_flag": True}, ) return len(response.json()["data"]) > 0 @@ -31,19 +33,28 @@ def test_quicksearch_officer_involved_shootings_philadelphia_county_results(): response = requests.get( f"{BASE_URL}/quick-search/Officer Involved Shootings/philadelphia county", headers=HEADERS, + json={"test_flag": True}, ) return len(response.json()["data"]) > 0 def test_quicksearch_all_allgeheny_results(): - response = requests.get(f"{BASE_URL}/quick-search/all/allegheny", headers=HEADERS) + response = requests.get( + f"{BASE_URL}/quick-search/all/allegheny", + headers=HEADERS, + json={"test_flag": True}, + ) return len(response.json()["data"]) > 0 def test_quicksearch_complaints_all_results(): - response = requests.get(f"{BASE_URL}/quick-search/complaints/all", headers=HEADERS) + response = requests.get( + f"{BASE_URL}/quick-search/complaints/all", + headers=HEADERS, + json={"test_flag": True}, + ) return len(response.json()["data"]) > 0 @@ -52,6 +63,7 @@ def test_quicksearch_media_bulletin_pennsylvania_results(): response = requests.get( f"{BASE_URL}/quick-search/media bulletin/pennsylvania", headers=HEADERS, + json={"test_flag": True}, ) return len(response.json()["data"]) > 0 diff --git a/resources/DataSources.py b/resources/DataSources.py index eacf739c..70c5f80a 100644 --- a/resources/DataSources.py +++ b/resources/DataSources.py @@ -19,7 +19,10 @@ def get(self, data_source_id): conn=self.psycopg2_connection, data_source_id=data_source_id ) if data_source_details: - return data_source_details + return { + "message": "Successfully found data source", + "data": data_source_details, + } else: return {"message": "Data source not found."}, 404 diff --git a/resources/QuickSearch.py b/resources/QuickSearch.py index a782b3b6..645cbd15 100644 --- a/resources/QuickSearch.py +++ b/resources/QuickSearch.py @@ -5,6 +5,7 @@ import json import os from middleware.initialize_psycopg2_connection import initialize_psycopg2_connection +from flask import request class QuickSearch(Resource): @@ -15,9 +16,15 @@ def __init__(self, **kwargs): # A user can get an API key by signing up and logging in (see User.py) @api_required def get(self, search, location): + try: + data = request.get_json() + test = data.get("test_flag") + except: + test = False + try: data_sources = quick_search_query( - search, location, [], self.psycopg2_connection + search, location, [], self.psycopg2_connection, test ) if data_sources["count"] == 0: @@ -29,10 +36,13 @@ def get(self, search, location): if data_sources["count"] == 0: return { "count": 0, - message: "No results found. Please considering requesting a new data source.", + "message": "No results found. Please considering requesting a new data source.", }, 404 - return data_sources + return { + "message": "Results for search successfully retrieved", + "data": data_sources, + } except Exception as e: self.psycopg2_connection.rollback() From 5f16d4b66a4043d8325f678c8fdec6544b549d6d Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 19 Feb 2024 16:27:36 -0500 Subject: [PATCH 18/73] move login to own endpoint --- app.py | 4 ++++ app_test.py | 16 ++++++++-------- middleware/login_queries.py | 14 ++++++++++++++ middleware/user_queries.py | 16 ---------------- regular_api_checks.py | 17 +++++++++-------- resources/ApiKey.py | 4 ++-- resources/Login.py | 36 ++++++++++++++++++++++++++++++++++++ resources/User.py | 28 ++-------------------------- 8 files changed, 75 insertions(+), 60 deletions(-) create mode 100644 middleware/login_queries.py create mode 100644 resources/Login.py diff --git a/app.py b/app.py index 0db30f35..0fa0f382 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ from flask_restful import Api from flask_cors import CORS from resources.User import User +from resources.Login import Login from resources.ApiKey import ApiKey from resources.RequestResetPassword import RequestResetPassword from resources.ResetPassword import ResetPassword @@ -22,6 +23,9 @@ api.add_resource( User, "/user", resource_class_kwargs={"psycopg2_connection": psycopg2_connection} ) +api.add_resource( + Login, "/login", resource_class_kwargs={"psycopg2_connection": psycopg2_connection} +) api.add_resource( ApiKey, "/api_key", diff --git a/app_test.py b/app_test.py index 5f20ae0c..e6b0f1cc 100644 --- a/app_test.py +++ b/app_test.py @@ -15,10 +15,10 @@ DATA_SOURCES_APPROVED_COLUMNS, ) from middleware.user_queries import ( - user_get_results, user_post_results, user_check_email, ) +from middleware.login_queries import login_results from middleware.archives_queries import ( archives_get_results, archives_get_query, @@ -134,13 +134,6 @@ def test_data_source_by_id_approved(session): assert not response -def test_user_get_query(session): - curs = session.cursor() - user_data = user_get_results(curs, "test") - - assert user_data["password_digest"] - - def test_user_post_query(session): curs = session.cursor() user_post_results(curs, "unit_test", "unit_test") @@ -152,6 +145,13 @@ def test_user_post_query(session): assert email_check == "unit_test" +def test_login_query(session): + curs = session.cursor() + user_data = login_results(curs, "test") + + assert user_data["password_digest"] + + def test_user_check_email(session): curs = session.cursor() user_data = user_check_email(curs, "test") diff --git a/middleware/login_queries.py b/middleware/login_queries.py new file mode 100644 index 00000000..4110ff6e --- /dev/null +++ b/middleware/login_queries.py @@ -0,0 +1,14 @@ +def login_results(cursor, email): + cursor.execute( + f"select id, password_digest, api_key from users where email = '{email}'" + ) + results = cursor.fetchall() + if len(results) > 0: + user_data = { + "id": results[0][0], + "password_digest": results[0][1], + "api_key": results[0][2], + } + return user_data + else: + return {"error": "no match"} diff --git a/middleware/user_queries.py b/middleware/user_queries.py index 841a441e..ab9753a4 100644 --- a/middleware/user_queries.py +++ b/middleware/user_queries.py @@ -11,22 +11,6 @@ def user_check_email(cursor, email): return {"error": "no match"} -def user_get_results(cursor, email): - cursor.execute( - f"select id, password_digest, api_key from users where email = '{email}'" - ) - results = cursor.fetchall() - if len(results) > 0: - user_data = { - "id": results[0][0], - "password_digest": results[0][1], - "api_key": results[0][2], - } - return user_data - else: - return {"error": "no match"} - - def user_post_results(cursor, email, password): password_digest = generate_password_hash(password) cursor.execute( diff --git a/regular_api_checks.py b/regular_api_checks.py index 59f8a54e..a3d3d42e 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -148,24 +148,25 @@ def test_search_tokens_quick_search_complaints_allegheny_results(): # user -def test_get_user(): - response = requests.get( +def test_put_user(): + response = requests.put( f"{BASE_URL}/user", headers=HEADERS, json={"email": "test2", "password": "test"}, ) - return response.json()["message"] == "Successfully logged in" + return response.json()["message"] == "Successfully updated password" -def test_put_user(): - response = requests.put( - f"{BASE_URL}/user", +# login +def test_login(): + response = requests.post( + f"{BASE_URL}/login", headers=HEADERS, json={"email": "test2", "password": "test"}, ) - return response.json()["message"] == "Successfully updated password" + return response.json()["message"] == "Successfully logged in" # reset-password @@ -268,8 +269,8 @@ def main(): "test_search_tokens_data_sources", "test_search_tokens_data_source_by_id", "test_search_tokens_quick_search_complaints_allegheny_results", - "test_get_user", "test_put_user", + "test_login", "test_request_reset_password", "test_get_api_key", "test_get_archives", diff --git a/resources/ApiKey.py b/resources/ApiKey.py index d0609e21..77081f5f 100644 --- a/resources/ApiKey.py +++ b/resources/ApiKey.py @@ -1,7 +1,7 @@ from werkzeug.security import check_password_hash from flask_restful import Resource from flask import request -from middleware.user_queries import user_get_results +from middleware.login_queries import login_results import uuid @@ -18,7 +18,7 @@ def get(self): email = data.get("email") password = data.get("password") cursor = self.psycopg2_connection.cursor() - user_data = user_get_results(cursor, email) + user_data = login_results(cursor, email) if check_password_hash(user_data["password_digest"], password): api_key = uuid.uuid4().hex diff --git a/resources/Login.py b/resources/Login.py new file mode 100644 index 00000000..8842307d --- /dev/null +++ b/resources/Login.py @@ -0,0 +1,36 @@ +from werkzeug.security import check_password_hash +from flask_restful import Resource +from flask import request +from middleware.login_queries import login_results + + +class Login(Resource): + def __init__(self, **kwargs): + self.psycopg2_connection = kwargs["psycopg2_connection"] + + def post(self): + """ + Login function: allows a user to login using their email and password as credentials + The password is compared to the hashed password stored in the users table + Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user + """ + try: + data = request.get_json() + email = data.get("email") + password = data.get("password") + cursor = self.psycopg2_connection.cursor() + + user_data = login_results(cursor, email) + + if "password_digest" in user_data and check_password_hash( + user_data["password_digest"], password + ): + return { + "message": "Successfully logged in", + "data": user_data["api_key"], + } + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"message": str(e)}, 500 diff --git a/resources/User.py b/resources/User.py index 186794e2..c2f2ca15 100644 --- a/resources/User.py +++ b/resources/User.py @@ -1,37 +1,13 @@ -from werkzeug.security import generate_password_hash, check_password_hash +from werkzeug.security import generate_password_hash from flask_restful import Resource from flask import request -from middleware.user_queries import user_get_results, user_post_results +from middleware.user_queries import user_post_results class User(Resource): def __init__(self, **kwargs): self.psycopg2_connection = kwargs["psycopg2_connection"] - def get(self): - """ - Login function: allows a user to login using their email and password as credentials - The password is compared to the hashed password stored in the users table - Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user - """ - try: - data = request.get_json() - email = data.get("email") - password = data.get("password") - cursor = self.psycopg2_connection.cursor() - - user_data = user_get_results(cursor, email) - if check_password_hash(user_data["password_digest"], password): - return { - "message": "Successfully logged in", - "data": user_data["api_key"], - } - - except Exception as e: - self.psycopg2_connection.rollback() - print(str(e)) - return {"message": str(e)}, 500 - def post(self): """ Sign up function: allows a user to sign up by submitting an email and password. From abbeea966dd70c502812c53d43dd7feaac83c054 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Tue, 20 Feb 2024 15:56:34 -0500 Subject: [PATCH 19/73] session token --- resources/Login.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/resources/Login.py b/resources/Login.py index 8842307d..bc665de3 100644 --- a/resources/Login.py +++ b/resources/Login.py @@ -2,6 +2,9 @@ from flask_restful import Resource from flask import request from middleware.login_queries import login_results +import jwt +import os +import datetime class Login(Resource): @@ -25,9 +28,18 @@ def post(self): if "password_digest" in user_data and check_password_hash( user_data["password_digest"], password ): + payload = { + "exp": datetime.datetime.utcnow() + + datetime.timedelta(days=0, seconds=300), + "iat": datetime.datetime.utcnow(), + "sub": 1, + } + session_token = jwt.encode( + payload, os.getenv("SECRET_KEY"), algorithm="HS256" + ) return { "message": "Successfully logged in", - "data": user_data["api_key"], + "data": session_token, } except Exception as e: From cbb745972c33af1eab0610c86cf91c803ec89dc0 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Thu, 22 Feb 2024 15:34:42 -0500 Subject: [PATCH 20/73] refresh session tokens --- app.py | 6 +++++ middleware/login_queries.py | 47 ++++++++++++++++++++++++++++++++++ middleware/security.py | 48 ++++++++++++++++++++++++++--------- resources/Login.py | 50 +++++++++++++++---------------------- resources/RefreshSession.py | 40 +++++++++++++++++++++++++++++ resources/SearchTokens.py | 7 ++++-- 6 files changed, 154 insertions(+), 44 deletions(-) create mode 100644 resources/RefreshSession.py diff --git a/app.py b/app.py index 0fa0f382..ca8560cf 100644 --- a/app.py +++ b/app.py @@ -3,6 +3,7 @@ from flask_cors import CORS from resources.User import User from resources.Login import Login +from resources.RefreshSession import RefreshSession from resources.ApiKey import ApiKey from resources.RequestResetPassword import RequestResetPassword from resources.ResetPassword import ResetPassword @@ -26,6 +27,11 @@ api.add_resource( Login, "/login", resource_class_kwargs={"psycopg2_connection": psycopg2_connection} ) +api.add_resource( + RefreshSession, + "/refresh-session", + resource_class_kwargs={"psycopg2_connection": psycopg2_connection}, +) api.add_resource( ApiKey, "/api_key", diff --git a/middleware/login_queries.py b/middleware/login_queries.py index 4110ff6e..82c4031f 100644 --- a/middleware/login_queries.py +++ b/middleware/login_queries.py @@ -1,3 +1,8 @@ +import jwt +import os +import datetime + + def login_results(cursor, email): cursor.execute( f"select id, password_digest, api_key from users where email = '{email}'" @@ -12,3 +17,45 @@ def login_results(cursor, email): return user_data else: return {"error": "no match"} + + +def is_admin(cursor, email): + cursor.execute(f"select role from users where email = '{email}'") + results = cursor.fetchall() + if len(results) > 0: + role = results[0][0] + if role == "admin": + return True + return False + + else: + return {"error": "no match"} + + +def create_session_token(cursor, id, email): + expiration = datetime.datetime.utcnow() + datetime.timedelta(days=0, seconds=300) + payload = { + "exp": expiration, + "iat": datetime.datetime.utcnow(), + "sub": id, + } + session_token = jwt.encode(payload, os.getenv("SECRET_KEY"), algorithm="HS256") + cursor.execute( + f"insert into session_tokens (token, email, expiration_date) values (%s, %s, %s)", + (session_token, email, expiration), + ) + + return session_token + + +def token_results(cursor, token): + cursor.execute(f"select id, email from session_tokens where token = '{token}'") + results = cursor.fetchall() + if len(results) > 0: + user_data = { + "id": results[0][0], + "email": results[0][1], + } + return user_data + else: + return {"error": "no match"} diff --git a/middleware/security.py b/middleware/security.py index 9a01fbea..c7b32173 100644 --- a/middleware/security.py +++ b/middleware/security.py @@ -3,6 +3,7 @@ from flask import request, jsonify from middleware.initialize_psycopg2_connection import initialize_psycopg2_connection from datetime import datetime as dt +from middleware.login_queries import is_admin import os @@ -10,32 +11,52 @@ def is_valid(api_key, endpoint, method): """ Get the user data that matches the API key from the request """ + if not api_key: + return False, False + psycopg2_connection = initialize_psycopg2_connection() cursor = psycopg2_connection.cursor() cursor.execute(f"select id, api_key, role from users where api_key = '{api_key}'") results = cursor.fetchall() - user_data = {} - if endpoint in ("datasources", "datasourcebyid") and method in ("PUT", "POST"): - if results[0][2] != "admin": - return False + if len(results) > 0: + role = results[0][2] - elif not results: + if not results: cursor.execute( - f"delete from access_tokens where expiration_date < '{dt.now()}'" + f"select email, expiration_date from session_tokens where token = '{api_key}'" ) - psycopg2_connection.commit() + results = cursor.fetchall() + if len(results) > 0: + email = results[0][0] + expiration_date = results[0][1] + print(expiration_date, dt.utcnow()) + + if expiration_date < dt.utcnow(): + return False, True + + if is_admin(cursor, email): + role = "admin" + + if not results: cursor.execute(f"select id, token from access_tokens where token = '{api_key}'") results = cursor.fetchall() + cursor.execute( + f"delete from access_tokens where expiration_date < '{dt.utcnow()}'" + ) + psycopg2_connection.commit() + role = "user" if not results: - return False + return False, False + + if endpoint in ("datasources", "datasourcebyid") and method in ("PUT", "POST"): + if role != "admin": + return False, False - user_data = dict(zip(("id", "api_key"), results[0])) # Compare the API key in the user table to the API in the request header and proceed # through the protected route if it's valid. Otherwise, compare_digest will return False # and api_required will send an error message to provide a valid API key - if compare_digest(user_data.get("api_key"), api_key): - return True + return True, False def api_required(func): @@ -64,9 +85,12 @@ def decorator(*args, **kwargs): "message": "Please provide an 'Authorization' key in the request header" }, 400 # Check if API key is correct and valid - if is_valid(api_key, request.endpoint, request.method): + valid, expired = is_valid(api_key, request.endpoint, request.method) + if valid: return func(*args, **kwargs) else: + if expired: + return {"message": "The provided API key has expired"}, 401 return {"message": "The provided API key is not valid"}, 403 return decorator diff --git a/resources/Login.py b/resources/Login.py index bc665de3..a198bcc2 100644 --- a/resources/Login.py +++ b/resources/Login.py @@ -1,10 +1,7 @@ from werkzeug.security import check_password_hash from flask_restful import Resource from flask import request -from middleware.login_queries import login_results -import jwt -import os -import datetime +from middleware.login_queries import login_results, create_session_token class Login(Resource): @@ -17,32 +14,25 @@ def post(self): The password is compared to the hashed password stored in the users table Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user """ - try: - data = request.get_json() - email = data.get("email") - password = data.get("password") - cursor = self.psycopg2_connection.cursor() + # try: + data = request.get_json() + email = data.get("email") + password = data.get("password") + cursor = self.psycopg2_connection.cursor() - user_data = login_results(cursor, email) + user_data = login_results(cursor, email) - if "password_digest" in user_data and check_password_hash( - user_data["password_digest"], password - ): - payload = { - "exp": datetime.datetime.utcnow() - + datetime.timedelta(days=0, seconds=300), - "iat": datetime.datetime.utcnow(), - "sub": 1, - } - session_token = jwt.encode( - payload, os.getenv("SECRET_KEY"), algorithm="HS256" - ) - return { - "message": "Successfully logged in", - "data": session_token, - } + if "password_digest" in user_data and check_password_hash( + user_data["password_digest"], password + ): + token = create_session_token(cursor, user_data["id"], email) + self.psycopg2_connection.commit() + return { + "message": "Successfully logged in", + "data": token, + } - except Exception as e: - self.psycopg2_connection.rollback() - print(str(e)) - return {"message": str(e)}, 500 + # except Exception as e: + # self.psycopg2_connection.rollback() + # print(str(e)) + # return {"message": str(e)}, 500 diff --git a/resources/RefreshSession.py b/resources/RefreshSession.py new file mode 100644 index 00000000..60a643f6 --- /dev/null +++ b/resources/RefreshSession.py @@ -0,0 +1,40 @@ +from flask_restful import Resource +from flask import request +from middleware.login_queries import token_results, create_session_token +from datetime import datetime as dt + + +class RefreshSession(Resource): + def __init__(self, **kwargs): + self.psycopg2_connection = kwargs["psycopg2_connection"] + + def post(self): + """ + Login function: allows a user to login using their email and password as credentials + The password is compared to the hashed password stored in the users table + Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user + """ + try: + data = request.get_json() + old_token = data.get("session_token") + cursor = self.psycopg2_connection.cursor() + user_data = token_results(cursor, old_token) + cursor.execute( + f"delete from session_tokens where token = '{old_token}' and expiration_date < '{dt.utcnow()}'" + ) + self.psycopg2_connection.commit() + + if "id" in user_data: + token = create_session_token( + cursor, user_data["id"], user_data["email"] + ) + self.psycopg2_connection.commit() + return { + "message": "Successfully refreshed session token", + "data": token, + } + + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"message": str(e)}, 500 diff --git a/resources/SearchTokens.py b/resources/SearchTokens.py index 5ee44390..8ee99240 100644 --- a/resources/SearchTokens.py +++ b/resources/SearchTokens.py @@ -32,8 +32,11 @@ def get(self): cursor = self.psycopg2_connection.cursor() token = uuid.uuid4().hex expiration = datetime.datetime.now() + datetime.timedelta(minutes=5) - # cursor.execute(f"insert into access_tokens (token, expiration_date) values (%s, %s)", (token, expiration)) - # self.psycopg2_connection.commit() + cursor.execute( + f"insert into access_tokens (token, expiration_date) values (%s, %s)", + (token, expiration), + ) + self.psycopg2_connection.commit() if endpoint == "quick-search": try: From 3db5e9a7c318413a52a4f4d5ff705a9f875490bc Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 07:10:02 -0500 Subject: [PATCH 21/73] style(pages): miscellaneous code cleanup --- client/src/pages/LogIn.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/pages/LogIn.vue b/client/src/pages/LogIn.vue index 7c90250e..64a1a62f 100644 --- a/client/src/pages/LogIn.vue +++ b/client/src/pages/LogIn.vue @@ -123,7 +123,7 @@ export default { [FORM_TYPES.signup]: SIGNUP_SCHEMA, }, // Adding enum-like obj for use in markup - FORM_TYPES: FORM_TYPES, + FORM_TYPES, loading: false, success: undefined, url: `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`, @@ -142,6 +142,7 @@ export default { this.handlePasswordValidation(formValues); } }, + /** * When signing up: validates that passwords match * @returns {boolean} `false` if passwords do not match, `true` if they do @@ -157,11 +158,13 @@ export default { return true; } }, + async login(data) { return await axios.get(this.url, data, { headers: { 'Content-Type': 'application/json' }, }); }, + async signup(data) { // Destructure to remove "confirmPassword" field — backend doesn't need it. const { email, password } = data; @@ -177,6 +180,7 @@ export default { }, ); }, + /** * Logs user in or signs user up */ @@ -201,6 +205,7 @@ export default { this.loading = false; } }, + /** * Toggles between login and signup actions */ From 5dbace436dc75cee9048dcd105365b50dda721ba Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 07:13:41 -0500 Subject: [PATCH 22/73] chore(deps): bump vue, add pinia --- client/package-lock.json | 173 +++++++++++++++++++++++++-------------- client/package.json | 3 +- 2 files changed, 114 insertions(+), 62 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index ae3a1823..8c788d3f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0", "dependencies": { "axios": "^1.6.0", - "vue": "^3.3.10", + "pinia": "^2.1.7", + "vue": "^3.4.19", "vue-router": "^4.2.4" }, "devDependencies": { @@ -1127,12 +1128,12 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", + "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.19", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" @@ -1144,26 +1145,26 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", + "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-core": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", - "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", + "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.19", + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", + "magic-string": "^0.30.6", "postcss": "^8.4.33", "source-map-js": "^1.0.2" } @@ -1174,12 +1175,12 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", + "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/devtools-api": { @@ -1202,48 +1203,48 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", + "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", "dependencies": { - "@vue/shared": "3.4.15" + "@vue/shared": "3.4.19" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", + "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/reactivity": "3.4.19", + "@vue/shared": "3.4.19" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", + "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", + "@vue/runtime-core": "3.4.19", + "@vue/shared": "3.4.19", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", + "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19" }, "peerDependencies": { - "vue": "3.4.15" + "vue": "3.4.19" } }, "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==" + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", + "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==" }, "node_modules/@vue/test-utils": { "version": "2.4.4", @@ -3393,9 +3394,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -4030,6 +4031,56 @@ "node": ">=0.10.0" } }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -6317,15 +6368,15 @@ } }, "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", - "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", + "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", + "dependencies": { + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-sfc": "3.4.19", + "@vue/runtime-dom": "3.4.19", + "@vue/server-renderer": "3.4.19", + "@vue/shared": "3.4.19" }, "peerDependencies": { "typescript": "*" diff --git a/client/package.json b/client/package.json index 301d8c64..0ae42424 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,8 @@ }, "dependencies": { "axios": "^1.6.0", - "vue": "^3.3.10", + "pinia": "^2.1.7", + "vue": "^3.4.19", "vue-router": "^4.2.4" }, "devDependencies": { From 03b979728e69ef5e3ac946c8e2b1cf79f6e9f3ca Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 07:31:38 -0500 Subject: [PATCH 23/73] chore(deps): add jwt-decode --- client/package-lock.json | 9 +++++++++ client/package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index 8c788d3f..537f7c0a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "axios": "^1.6.0", + "jwt-decode": "^4.0.0", "pinia": "^2.1.7", "vue": "^3.4.19", "vue-router": "^4.2.4" @@ -3289,6 +3290,14 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/client/package.json b/client/package.json index 0ae42424..d4f93d93 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "axios": "^1.6.0", + "jwt-decode": "^4.0.0", "pinia": "^2.1.7", "vue": "^3.4.19", "vue-router": "^4.2.4" From 3c492d937ee011e6dee0a0e4f873c7f57512fce2 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 10:40:57 -0500 Subject: [PATCH 24/73] chore(deps): add lodash --- client/package-lock.json | 4 ++-- client/package.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 537f7c0a..a9eb6f37 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "axios": "^1.6.0", "jwt-decode": "^4.0.0", + "lodash": "^4.17.21", "pinia": "^2.1.7", "vue": "^3.4.19", "vue-router": "^4.2.4" @@ -3369,8 +3370,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", diff --git a/client/package.json b/client/package.json index d4f93d93..75f94714 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "dependencies": { "axios": "^1.6.0", "jwt-decode": "^4.0.0", + "lodash": "^4.17.21", "pinia": "^2.1.7", "vue": "^3.4.19", "vue-router": "^4.2.4" From 3a12b2a0f567d537a237ab6338d8bc82dd22d188 Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Fri, 23 Feb 2024 12:25:02 -0500 Subject: [PATCH 25/73] refresh test and fix --- regular_api_checks.py | 16 +++++++++++++++- resources/Login.py | 38 ++++++++++++++++++------------------- resources/RefreshSession.py | 2 ++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/regular_api_checks.py b/regular_api_checks.py index a3d3d42e..3f5d0ac1 100644 --- a/regular_api_checks.py +++ b/regular_api_checks.py @@ -162,13 +162,27 @@ def test_put_user(): def test_login(): response = requests.post( f"{BASE_URL}/login", - headers=HEADERS, json={"email": "test2", "password": "test"}, ) return response.json()["message"] == "Successfully logged in" +# refresh-session +def test_refresh_session(): + response = requests.post( + f"{BASE_URL}/login", + json={"email": "test2", "password": "test"}, + ) + token = response.json()["data"] + + response = requests.post( + f"{BASE_URL}/refresh-session", json={"session_token": token} + ) + + return response.json()["message"] == "Successfully refreshed session token" + + # reset-password def test_request_reset_password(): reset_token = requests.get( diff --git a/resources/Login.py b/resources/Login.py index a198bcc2..f28bfa1a 100644 --- a/resources/Login.py +++ b/resources/Login.py @@ -14,25 +14,25 @@ def post(self): The password is compared to the hashed password stored in the users table Once the password is verified, an API key is generated, which is stored in the users table and sent to the verified user """ - # try: - data = request.get_json() - email = data.get("email") - password = data.get("password") - cursor = self.psycopg2_connection.cursor() + try: + data = request.get_json() + email = data.get("email") + password = data.get("password") + cursor = self.psycopg2_connection.cursor() - user_data = login_results(cursor, email) + user_data = login_results(cursor, email) - if "password_digest" in user_data and check_password_hash( - user_data["password_digest"], password - ): - token = create_session_token(cursor, user_data["id"], email) - self.psycopg2_connection.commit() - return { - "message": "Successfully logged in", - "data": token, - } + if "password_digest" in user_data and check_password_hash( + user_data["password_digest"], password + ): + token = create_session_token(cursor, user_data["id"], email) + self.psycopg2_connection.commit() + return { + "message": "Successfully logged in", + "data": token, + } - # except Exception as e: - # self.psycopg2_connection.rollback() - # print(str(e)) - # return {"message": str(e)}, 500 + except Exception as e: + self.psycopg2_connection.rollback() + print(str(e)) + return {"message": str(e)}, 500 diff --git a/resources/RefreshSession.py b/resources/RefreshSession.py index 60a643f6..b13a82f4 100644 --- a/resources/RefreshSession.py +++ b/resources/RefreshSession.py @@ -34,6 +34,8 @@ def post(self): "data": token, } + return {"message": "Invalid session token"}, 403 + except Exception as e: self.psycopg2_connection.rollback() print(str(e)) From 65ea58e7e46cbe14849cf52de1d3faf8049c75ce Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:46:15 -0500 Subject: [PATCH 26/73] chore(deps): add persist state plugin for pinia --- client/package-lock.json | 9 +++++++++ client/package.json | 1 + 2 files changed, 10 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index a9eb6f37..c1a56c6c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,6 +12,7 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^3.2.1", "vue": "^3.4.19", "vue-router": "^4.2.4" }, @@ -4065,6 +4066,14 @@ } } }, + "node_modules/pinia-plugin-persistedstate": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz", + "integrity": "sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==", + "peerDependencies": { + "pinia": "^2.0.0" + } + }, "node_modules/pinia/node_modules/vue-demi": { "version": "0.14.7", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", diff --git a/client/package.json b/client/package.json index 75f94714..89588cd7 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^3.2.1", "vue": "^3.4.19", "vue-router": "^4.2.4" }, From 5cb220b3cf87c6867dd475263fc23bf8067efe92 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:49:11 -0500 Subject: [PATCH 27/73] feat(state): add pinia store for auth Add pinia and store, Update router to protect auth routes, Add util to parse JWT --- client/src/main.js | 8 +++- client/src/router.js | 16 +++++++ client/src/stores/auth.js | 90 +++++++++++++++++++++++++++++++++++++ client/src/util/parseJwt.js | 10 +++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 client/src/stores/auth.js create mode 100644 client/src/util/parseJwt.js diff --git a/client/src/main.js b/client/src/main.js index affa8e3d..7320b8d0 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -1,13 +1,19 @@ import './main.css'; import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import piniaPersistState from 'pinia-plugin-persistedstate'; + import App from './App.vue'; +import { FlexContainer } from 'pdap-design-system'; import router from './router'; import 'pdap-design-system/styles'; -import { FlexContainer } from 'pdap-design-system'; +const pinia = createPinia(); +pinia.use(piniaPersistState); const app = createApp(App); +app.use(pinia); app.use(router); // Register 'FlexContainer' so it can be passed as a grid item diff --git a/client/src/router.js b/client/src/router.js index d8322b30..e77da802 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -4,6 +4,9 @@ import SearchResultPage from '../src/pages/SearchResultPage.vue'; import DataSourceStaticView from '../src/pages/DataSourceStaticView.vue'; import ChangePassword from './pages/ChangePassword.vue'; import LogIn from './pages/LogIn.vue'; +import { useAuthStore } from './stores/auth'; + +export const PUBLIC_PAGES = ['/login', '/', '/data-sources', '/search']; const routes = [ { path: '/', component: QuickSearchPage, name: 'QuickSearchPage' }, @@ -34,4 +37,17 @@ const router = createRouter({ routes, }); +router.beforeEach(async (to) => { + // redirect to login page if not logged in and trying to access a restricted page + const auth = useAuthStore(); + + if ( + !PUBLIC_PAGES.some((path) => path.startsWith(to.fullPath)) && + !auth.userId + ) { + auth.returnUrl = to.path; + return '/login'; + } +}); + export default router; diff --git a/client/src/stores/auth.js b/client/src/stores/auth.js new file mode 100644 index 00000000..c4277b8a --- /dev/null +++ b/client/src/stores/auth.js @@ -0,0 +1,90 @@ +import axios from 'axios'; +import { defineStore } from 'pinia'; +import parseJwt from '../util/parseJwt'; +import router from '../router'; + +const HEADERS = { + headers: { 'Content-Type': 'application/json' }, +}; +const INITIAL_STATE = { + userId: null, + accessToken: { + value: null, + expires: Date.now(), + }, + returnUrl: null, +}; +const LOGIN_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/login`; +const SIGNUP_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`; +const REFRESH_SESSION_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/refresh-session`; + +export const useAuthStore = defineStore('auth', { + state: () => ({ ...INITIAL_STATE }), + persist: true, + actions: { + async login(email, password) { + try { + const response = await axios.post( + LOGIN_URL, + { email, password }, + HEADERS, + ); + + this.parseTokenAndSetData(response); + if (this.returnUrl) router.push(this.returnUrl); + } catch (error) { + throw new Error(error.message); + } + }, + + logout(isAuthRoute) { + this.$patch({ ...INITIAL_STATE }); + if (isAuthRoute) router.push('/login'); + }, + + async refreshAccessToken() { + if (!this.$state.userId) return; + try { + const response = await axios.post( + REFRESH_SESSION_URL, + { session_token: this.$state.accessToken.value }, + HEADERS, + ); + return this.parseTokenAndSetData(response); + } catch (error) { + throw new Error(error.message); + } + }, + + // We may eventually want to move signup to a separate "User" store, but as of now it would be the only thing in that store, so we'll wait for the time being + async signup(email, password) { + try { + await axios.post( + SIGNUP_URL, + { email, password }, + { + headers: { 'Content-Type': 'application/json' }, + }, + ); + + // Log users in after signup and return that response + return await this.login(email, password); + } catch (error) { + throw new Error(error.message); + } + }, + + parseTokenAndSetData(response) { + const token = response.data.data; + const tokenParsed = parseJwt(token); + + this.$patch({ + userId: tokenParsed.sub, + accessToken: { + value: token, + expires: new Date(tokenParsed.exp * 1000).getTime(), + }, + }); + }, + }, +}); diff --git a/client/src/util/parseJwt.js b/client/src/util/parseJwt.js new file mode 100644 index 00000000..afe44dd3 --- /dev/null +++ b/client/src/util/parseJwt.js @@ -0,0 +1,10 @@ +import { jwtDecode } from 'jwt-decode'; + +/** + * Util for parsing JSON Web Tokens + * @param {string} token JWT to be decoded + * @returns {{ expiration: number, iat: number, sub: string }} Decoded JWT + */ +export default function parseJwt(token) { + return jwtDecode(token); +} From 5cab8bd819689fa14d67ac76002fb79a0506949b Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:49:58 -0500 Subject: [PATCH 28/73] feat(components): add AuthWrapper component Listen for user interactions and automatically refresh token --- client/src/App.vue | 20 ++++++---- client/src/components/AuthWrapper.vue | 55 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 client/src/components/AuthWrapper.vue diff --git a/client/src/App.vue b/client/src/App.vue index f1593ea8..8852f31b 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,19 +1,23 @@ From 6cd2039e8888f8e40bc72561ec52c45106440863 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:51:02 -0500 Subject: [PATCH 29/73] refactor(pages): update login page Use composition API, Use new auth store --- client/src/pages/LogIn.vue | 220 ++++++++++++++++++------------------- 1 file changed, 105 insertions(+), 115 deletions(-) diff --git a/client/src/pages/LogIn.vue b/client/src/pages/LogIn.vue index 64a1a62f..621adbe9 100644 --- a/client/src/pages/LogIn.vue +++ b/client/src/pages/LogIn.vue @@ -1,9 +1,15 @@ - From e636d5ff19fe49a8130d8ed2a623a8c11ca1a02d Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:53:46 -0500 Subject: [PATCH 30/73] refactor(router): push to login rather than update route --- client/src/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/router.js b/client/src/router.js index e77da802..01fed5ba 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -46,7 +46,7 @@ router.beforeEach(async (to) => { !auth.userId ) { auth.returnUrl = to.path; - return '/login'; + router.push('/login'); } }); From a12e5b9724a3bdaac87bd63205087e05b818e667 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 13:56:29 -0500 Subject: [PATCH 31/73] docs(README): update client docs --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c297112..2d4389b5 100644 --- a/README.md +++ b/README.md @@ -108,14 +108,17 @@ pip install pytest pytest ``` - ## Linting Linting is enforced with black on PR creation. You can use black to automatically reformat your files before commiting them, this will allow your PR to pass this check. Any files that require reformatting will be listed on any failed checks on the PR. ``` black app_test.py ``` -## Other helpful commands for the client app +## Client App + +A few things to know: + +- We use Vue3. This allows for using either the options or composition APIs. Feel free to use whichever you are most fluent in. One caveat to this: We use `pinia` for state management. This works much better with the composition API than with options, so it is recommended to use the composition API if you need data from one of the `pinia` stores. ### Compiles and minifies for production ``` From e55e7af7d00214c566a983a6d7cf91feb902a8df Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Fri, 23 Feb 2024 14:02:50 -0500 Subject: [PATCH 32/73] unit tests --- app_test.py | 31 ++++++++++++++++++++++++++++++- do_db_ddl_clean.sql | 10 +++++++++- middleware/login_queries.py | 3 +-- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app_test.py b/app_test.py index e6b0f1cc..589693d3 100644 --- a/app_test.py +++ b/app_test.py @@ -18,7 +18,12 @@ user_post_results, user_check_email, ) -from middleware.login_queries import login_results +from middleware.login_queries import ( + login_results, + create_session_token, + token_results, + is_admin, +) from middleware.archives_queries import ( archives_get_results, archives_get_query, @@ -152,6 +157,30 @@ def test_login_query(session): assert user_data["password_digest"] +def test_create_session_token_results(session): + curs = session.cursor() + token = create_session_token(curs, 1, "test") + + curs = session.cursor() + new_token = token_results(curs, token) + + assert new_token["email"] + + +def test_is_admin(session): + curs = session.cursor() + admin = is_admin(curs, "mbodenator@gmail.com") + + assert admin + + +def test_not_admin(session): + curs = session.cursor() + admin = is_admin(curs, "test") + + assert not admin + + def test_user_check_email(session): curs = session.cursor() user_data = user_check_email(curs, "test") diff --git a/do_db_ddl_clean.sql b/do_db_ddl_clean.sql index acc0ebd8..2a7c4b03 100644 --- a/do_db_ddl_clean.sql +++ b/do_db_ddl_clean.sql @@ -140,7 +140,8 @@ CREATE TABLE if not exists users ( updated_at timestamp with time zone, email text NOT NULL, password_digest text, - api_key character varying + api_key character varying, + role text ); CREATE TABLE if not exists reset_tokens ( @@ -165,6 +166,13 @@ CREATE TABLE if not exists volunteers ( created timestamp without time zone NOT NULL ); +CREATE TABLE if not exists session_tokens ( + id serial primary key, + token text NOT NULL, + email text NOT NULL, + expiration_date timestamp with time zone NOT NULL +); + INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (1, 'rec00T2YLS2jU7Tbn', 'recv9fMNEQTbVarj2'); INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (2, 'rec8zJuEOvhAZCfAD', 'recxUlLdt3Wwov6P1'); INSERT INTO agency_source_link (link_id, airtable_uid, agency_described_linked_uid) VALUES (3, 'recUGIoPQbJ6laBmr', 'recv9fMNEQTbVarj2'); diff --git a/middleware/login_queries.py b/middleware/login_queries.py index 82c4031f..5be52340 100644 --- a/middleware/login_queries.py +++ b/middleware/login_queries.py @@ -41,8 +41,7 @@ def create_session_token(cursor, id, email): } session_token = jwt.encode(payload, os.getenv("SECRET_KEY"), algorithm="HS256") cursor.execute( - f"insert into session_tokens (token, email, expiration_date) values (%s, %s, %s)", - (session_token, email, expiration), + f"insert into session_tokens (token, email, expiration_date) values ('{session_token}', '{email}', '{expiration}')" ) return session_token From c34c4358904a4b54a97c764e3ffa1f49a4ebd9a2 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 14:05:36 -0500 Subject: [PATCH 33/73] chore(deps): pinia testing --- client/package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++++ client/package.json | 1 + 2 files changed, 43 insertions(+) diff --git a/client/package-lock.json b/client/package-lock.json index c1a56c6c..04d4cb92 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@pdap-design-system/eslint-config": "^1.0.1", + "@pinia/testing": "^0.1.3", "@vitejs/plugin-vue": "^4.2.3", "@vitest/coverage-v8": "^1.2.1", "@vue/eslint-config-prettier": "^8.0.0", @@ -737,6 +738,47 @@ "eslint": ">= 8" } }, + "node_modules/@pinia/testing": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.3.tgz", + "integrity": "sha512-D2Ds2s69kKFaRf2KCcP1NhNZEg5+we59aRyQalwRm7ygWfLM25nDH66267U3hNvRUOTx8ofL24GzodZkOmB5xw==", + "dev": true, + "dependencies": { + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "pinia": ">=2.1.5" + } + }, + "node_modules/@pinia/testing/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", diff --git a/client/package.json b/client/package.json index 89588cd7..fc4a8af6 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,7 @@ }, "devDependencies": { "@pdap-design-system/eslint-config": "^1.0.1", + "@pinia/testing": "^0.1.3", "@vitejs/plugin-vue": "^4.2.3", "@vitest/coverage-v8": "^1.2.1", "@vue/eslint-config-prettier": "^8.0.0", From f5899949fff97bfd61e8fd0d9d2e5627d5364b86 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 14:05:54 -0500 Subject: [PATCH 34/73] test(pages): update tests and snapshots --- .../__tests__/__snapshots__/app.test.js.snap | 143 +++++++++--------- .../quickSearchPage.test.js.snap | 4 +- client/src/pages/__tests__/app.test.js | 2 + 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/client/src/pages/__tests__/__snapshots__/app.test.js.snap b/client/src/pages/__tests__/__snapshots__/app.test.js.snap index 6fd5a6cc..30ab1216 100644 --- a/client/src/pages/__tests__/__snapshots__/app.test.js.snap +++ b/client/src/pages/__tests__/__snapshots__/app.test.js.snap @@ -1,87 +1,90 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`App > renders the App 1`] = ` -
    - -
    - - - -
    - -
    - + +
    `; diff --git a/client/src/pages/__tests__/__snapshots__/quickSearchPage.test.js.snap b/client/src/pages/__tests__/__snapshots__/quickSearchPage.test.js.snap index 91dd5e6c..1aa35e92 100644 --- a/client/src/pages/__tests__/__snapshots__/quickSearchPage.test.js.snap +++ b/client/src/pages/__tests__/__snapshots__/quickSearchPage.test.js.snap @@ -8,16 +8,18 @@ exports[`QuickSearchPage > is a Vue instance 1`] = `

    -
    +
    +
    +
    diff --git a/client/src/pages/__tests__/app.test.js b/client/src/pages/__tests__/app.test.js index dadf5fad..fb0e0b5c 100644 --- a/client/src/pages/__tests__/app.test.js +++ b/client/src/pages/__tests__/app.test.js @@ -2,6 +2,7 @@ import { mount, RouterLinkStub, RouterViewStub } from '@vue/test-utils'; import App from '../../App.vue'; import { beforeEach, describe, expect, it } from 'vitest'; import { links } from '../../util/links'; +import { createTestingPinia } from '@pinia/testing'; let wrapper; @@ -9,6 +10,7 @@ describe('App', () => { beforeEach(() => { wrapper = mount(App, { global: { + plugins: [createTestingPinia()], provide: { navLinks: links, footerLinks: links, From 788618dc670c396d65e73d288ffc8bdf94a79367 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 15:35:12 -0500 Subject: [PATCH 35/73] test(util): add test for parseJwt --- client/src/util/__tests__/parseJwt.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 client/src/util/__tests__/parseJwt.test.js diff --git a/client/src/util/__tests__/parseJwt.test.js b/client/src/util/__tests__/parseJwt.test.js new file mode 100644 index 00000000..5c47829e --- /dev/null +++ b/client/src/util/__tests__/parseJwt.test.js @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest'; +import parseJwt from '../parseJwt'; + +const token = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDg3MTc3NjAsImlhdCI6MTcwODcxNzQ2MCwic3ViIjo2OX0.vX3JKqlUb-L_IWEYyG7R0zlMnY-kj5py5XsUviyAJN4'; + +describe('parseJwt', () => { + it('should parse a valid Jwt', () => { + expect(parseJwt(token)).toEqual({ + exp: 1708717760, + iat: 1708717460, + sub: 69, + }); + }); +}); From 6f57ce12b52a76977ba2f57d026f1c59596c6766 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 15:36:20 -0500 Subject: [PATCH 36/73] test(components): add test for AuthWrapper --- client/src/components/AuthWrapper.vue | 3 +- .../__snapshots__/authWrapper.test.js.snap | 3 + .../components/__tests__/authWrapper.test.js | 60 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 client/src/components/__tests__/__snapshots__/authWrapper.test.js.snap create mode 100644 client/src/components/__tests__/authWrapper.test.js diff --git a/client/src/components/AuthWrapper.vue b/client/src/components/AuthWrapper.vue index 1df17440..2bd68c11 100644 --- a/client/src/components/AuthWrapper.vue +++ b/client/src/components/AuthWrapper.vue @@ -35,6 +35,7 @@ function handleAuthRefresh() { const now = Date.now(); const difference = auth.accessToken.expires - now; + /* c8 ignore next 6 */ if (difference > 0) { console.debug({ secondsUntilRefreshOnUserActivity: (difference - 60000) / 1000, @@ -49,7 +50,5 @@ function handleAuthRefresh() { } else if (difference <= 0 && auth.userId) { return auth.logout(isAuthRoute); } - // Otherwise, return nothing. - return; } diff --git a/client/src/components/__tests__/__snapshots__/authWrapper.test.js.snap b/client/src/components/__tests__/__snapshots__/authWrapper.test.js.snap new file mode 100644 index 00000000..e1ccdd97 --- /dev/null +++ b/client/src/components/__tests__/__snapshots__/authWrapper.test.js.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AuthWrapper > renders auth wrapper 1`] = `
    `; diff --git a/client/src/components/__tests__/authWrapper.test.js b/client/src/components/__tests__/authWrapper.test.js new file mode 100644 index 00000000..ed8dcff1 --- /dev/null +++ b/client/src/components/__tests__/authWrapper.test.js @@ -0,0 +1,60 @@ +import { mount } from '@vue/test-utils'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import AuthWrapper from '../AuthWrapper.vue'; +import { createTestingPinia } from '@pinia/testing'; +import { useAuthStore } from '../../stores/auth'; +import { nextTick } from 'vue'; + +let wrapper; + +const NOW = Date.now(); +const NOW_MINUS_THIRTY = NOW - 30 * 1000; +const NOW_PLUS_THIRTY = NOW + 30 * 1000; + +describe('AuthWrapper', () => { + beforeEach(() => { + wrapper = mount(AuthWrapper, { + // props: { dataSource }, + global: { + plugins: [createTestingPinia()], + }, + }); + + vi.unstubAllGlobals(); + }); + + it('renders auth wrapper', () => { + expect(wrapper.find('[id="wrapper"]').exists()).toBe(true); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('refreshes access token when less than 1 minute remaining before access token expiry on event', async () => { + const auth = useAuthStore(); + auth.$patch({ + userId: 42, + accessToken: { + value: 'asdfasdfas', + expires: NOW_PLUS_THIRTY, + }, + }); + + await wrapper.trigger('click'); + await nextTick(); + expect(auth.refreshAccessToken).toHaveBeenCalled(); + }); + + it('logs user out when access token is expired on all expected events', async () => { + const auth = useAuthStore(); + auth.$patch({ + userId: 42, + accessToken: { + value: 'asdfasdfas', + expires: NOW_MINUS_THIRTY, + }, + }); + + await wrapper.trigger('click'); + await nextTick(); + expect(auth.logout).toHaveBeenCalled(); + }); +}); From a84a6cf544409e7d9db326366a7341bec50a985b Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 16:07:01 -0500 Subject: [PATCH 37/73] fix(router): public route logic conflicting Check private routes instead --- client/src/components/AuthWrapper.vue | 4 ++-- client/src/router.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/AuthWrapper.vue b/client/src/components/AuthWrapper.vue index 2bd68c11..98c94fc7 100644 --- a/client/src/components/AuthWrapper.vue +++ b/client/src/components/AuthWrapper.vue @@ -8,7 +8,7 @@ import debounce from 'lodash/debounce'; import { useAuthStore } from '../stores/auth'; import { useRoute } from 'vue-router'; -import { PUBLIC_PAGES } from '../router'; +import { PRIVATE_ROUTES } from '../router'; const route = useRoute(); const auth = useAuthStore(); @@ -30,7 +30,7 @@ const handlers = { }; function handleAuthRefresh() { - const isAuthRoute = !PUBLIC_PAGES.some((path) => path.startsWith(route)); + const isAuthRoute = PRIVATE_ROUTES.includes(route); const now = Date.now(); const difference = auth.accessToken.expires - now; diff --git a/client/src/router.js b/client/src/router.js index 01fed5ba..e1d04f01 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -6,7 +6,7 @@ import ChangePassword from './pages/ChangePassword.vue'; import LogIn from './pages/LogIn.vue'; import { useAuthStore } from './stores/auth'; -export const PUBLIC_PAGES = ['/login', '/', '/data-sources', '/search']; +export const PRIVATE_ROUTES = ['/change-password']; const routes = [ { path: '/', component: QuickSearchPage, name: 'QuickSearchPage' }, @@ -42,7 +42,8 @@ router.beforeEach(async (to) => { const auth = useAuthStore(); if ( - !PUBLIC_PAGES.some((path) => path.startsWith(to.fullPath)) && + to.fullPath.length > 1 && + PRIVATE_ROUTES.includes(to.fullPath) && !auth.userId ) { auth.returnUrl = to.path; From 94ce64ba09ed4a98ac5fc9cab4949fd03efd6a1b Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 16:11:09 -0500 Subject: [PATCH 38/73] chore(router): cleanup: remove unnecessary check --- client/src/router.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/src/router.js b/client/src/router.js index e1d04f01..7cc62fbf 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -41,11 +41,7 @@ router.beforeEach(async (to) => { // redirect to login page if not logged in and trying to access a restricted page const auth = useAuthStore(); - if ( - to.fullPath.length > 1 && - PRIVATE_ROUTES.includes(to.fullPath) && - !auth.userId - ) { + if (PRIVATE_ROUTES.includes(to.fullPath) && !auth.userId) { auth.returnUrl = to.path; router.push('/login'); } From 09c0b65f6561bc92916887c8465d363204a99092 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 16:18:37 -0500 Subject: [PATCH 39/73] fix(store): update logout patch --- client/src/stores/auth.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/src/stores/auth.js b/client/src/stores/auth.js index c4277b8a..f77a66a7 100644 --- a/client/src/stores/auth.js +++ b/client/src/stores/auth.js @@ -6,20 +6,19 @@ import router from '../router'; const HEADERS = { headers: { 'Content-Type': 'application/json' }, }; -const INITIAL_STATE = { - userId: null, - accessToken: { - value: null, - expires: Date.now(), - }, - returnUrl: null, -}; const LOGIN_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/login`; const SIGNUP_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`; const REFRESH_SESSION_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/refresh-session`; export const useAuthStore = defineStore('auth', { - state: () => ({ ...INITIAL_STATE }), + state: () => ({ + userId: null, + accessToken: { + value: null, + expires: Date.now(), + }, + returnUrl: null, + }), persist: true, actions: { async login(email, password) { @@ -38,7 +37,10 @@ export const useAuthStore = defineStore('auth', { }, logout(isAuthRoute) { - this.$patch({ ...INITIAL_STATE }); + this.$patch({ + userId: null, + accessToken: { value: null, expires: Date.now() }, + }); if (isAuthRoute) router.push('/login'); }, From 5f13ba57aaa613e62441a1a998e97e8fb6636f74 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 23 Feb 2024 16:19:36 -0500 Subject: [PATCH 40/73] fix(store): one more update to logout --- client/src/stores/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/stores/auth.js b/client/src/stores/auth.js index f77a66a7..f180d081 100644 --- a/client/src/stores/auth.js +++ b/client/src/stores/auth.js @@ -40,6 +40,7 @@ export const useAuthStore = defineStore('auth', { this.$patch({ userId: null, accessToken: { value: null, expires: Date.now() }, + returnUrl: null, }); if (isAuthRoute) router.push('/login'); }, From f6cf8f7c59317cb1092e673dbfda704ef5a7eff8 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 07:26:00 -0500 Subject: [PATCH 41/73] feat(stores): create user store move signup func add password change / reset logic update LogIn --- client/src/pages/LogIn.vue | 133 ++++++++++++++++++++----------------- client/src/stores/auth.js | 25 ++----- client/src/stores/user.js | 64 ++++++++++++++++++ 3 files changed, 141 insertions(+), 81 deletions(-) create mode 100644 client/src/stores/user.js diff --git a/client/src/pages/LogIn.vue b/client/src/pages/LogIn.vue index 621adbe9..fb2586ef 100644 --- a/client/src/pages/LogIn.vue +++ b/client/src/pages/LogIn.vue @@ -1,65 +1,57 @@ @@ -68,6 +60,7 @@ import { Button, Form } from 'pdap-design-system'; import { ref } from 'vue'; import { useAuthStore } from '../stores/auth'; +import { useUserStore } from '../stores/user'; // Constants const LOGIN_SCHEMA = [ @@ -137,6 +130,7 @@ const FORM_SCHEMAS = { // Store const auth = useAuthStore(); +const user = useUserStore(); // Reactive vars const error = ref(undefined); @@ -144,6 +138,8 @@ const loading = ref(false); const success = ref(undefined); const type = ref(FORM_TYPES.login); +// Functions +// Handlers /** * When signing up: handles clearing pw-match form errors on change if they exist */ @@ -187,7 +183,7 @@ async function onSubmit(formValues) { const response = type.value === FORM_TYPES.signup - ? await auth.signup(email, password) + ? await user.signup(email, password) : await auth.login(email, password); success.value = SUCCESS_COPY[type.value] ?? response.message; @@ -198,6 +194,7 @@ async function onSubmit(formValues) { } } +// Utils /** * Toggles between login and signup actions */ @@ -213,4 +210,16 @@ function toggleType() { return; } } + +function getSubmitButtonCopy() { + switch (true) { + case loading: + return 'Loading...'; + case type.value === FORM_TYPES.signup: + return 'Sign up'; + case type.value === FORM_TYPES.login: + default: + return 'Login'; + } +} diff --git a/client/src/stores/auth.js b/client/src/stores/auth.js index f180d081..e9d4e5af 100644 --- a/client/src/stores/auth.js +++ b/client/src/stores/auth.js @@ -2,12 +2,12 @@ import axios from 'axios'; import { defineStore } from 'pinia'; import parseJwt from '../util/parseJwt'; import router from '../router'; +import { useUserStore } from './user'; const HEADERS = { headers: { 'Content-Type': 'application/json' }, }; const LOGIN_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/login`; -const SIGNUP_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`; const REFRESH_SESSION_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/refresh-session`; export const useAuthStore = defineStore('auth', { @@ -22,6 +22,8 @@ export const useAuthStore = defineStore('auth', { persist: true, actions: { async login(email, password) { + const user = useUserStore(); + try { const response = await axios.post( LOGIN_URL, @@ -29,6 +31,9 @@ export const useAuthStore = defineStore('auth', { HEADERS, ); + // Update user store with email + user.$patch({ email }); + this.parseTokenAndSetData(response); if (this.returnUrl) router.push(this.returnUrl); } catch (error) { @@ -59,24 +64,6 @@ export const useAuthStore = defineStore('auth', { } }, - // We may eventually want to move signup to a separate "User" store, but as of now it would be the only thing in that store, so we'll wait for the time being - async signup(email, password) { - try { - await axios.post( - SIGNUP_URL, - { email, password }, - { - headers: { 'Content-Type': 'application/json' }, - }, - ); - - // Log users in after signup and return that response - return await this.login(email, password); - } catch (error) { - throw new Error(error.message); - } - }, - parseTokenAndSetData(response) { const token = response.data.data; const tokenParsed = parseJwt(token); diff --git a/client/src/stores/user.js b/client/src/stores/user.js new file mode 100644 index 00000000..423a35ff --- /dev/null +++ b/client/src/stores/user.js @@ -0,0 +1,64 @@ +import axios from 'axios'; +import { defineStore } from 'pinia'; +import { useAuthStore } from './auth'; + +const HEADERS = { + headers: { 'Content-Type': 'application/json' }, +}; +const SIGNUP_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`; +const CHANGE_PASSWORD_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/user`; +const REQUEST_PASSWORD_RESET_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/request-reset-password`; +const PASSWORD_RESET_URL = `${import.meta.env.VITE_VUE_APP_BASE_URL}/reset-password`; + +export const useUserStore = defineStore('user', { + state: () => ({ + email: '', + }), + persist: true, + actions: { + async signup(email, password) { + const auth = useAuthStore(); + + try { + await axios.post(SIGNUP_URL, { email, password }, HEADERS); + // Update store with email + this.$patch({ email }); + // Log users in after signup and return that response + return await auth.login(email, password); + } catch (error) { + throw new Error(error.message); + } + }, + + async changePassword(email, password) { + try { + await axios.put(CHANGE_PASSWORD_URL, { email, password }, HEADERS); + } catch (error) { + throw new Error(error.message); + } + }, + + async requestPasswordReset(email) { + try { + await axios.get(REQUEST_PASSWORD_RESET_URL, { email }, HEADERS); + } catch (error) { + throw new Error(error.message); + } + }, + + async resetPassword(email, password, token) { + try { + const resetResponse = await axios.put( + `${PASSWORD_RESET_URL}/${token}`, + HEADERS, + ); + + if (400 < resetResponse.status > 200) { + return await this.changePassword(email, password); + } + } catch (error) { + throw new Error(error.message); + } + }, + }, +}); From a277f126714a04b706cd296151e358760476d52d Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 08:12:12 -0500 Subject: [PATCH 42/73] feat(pages): build change password route --- client/src/pages/ChangePassword.vue | 116 +++++++++++++++++++++++++++- client/src/stores/auth.js | 10 ++- client/src/stores/user.js | 10 +-- 3 files changed, 126 insertions(+), 10 deletions(-) diff --git a/client/src/pages/ChangePassword.vue b/client/src/pages/ChangePassword.vue index 1d13abb1..4731c67b 100644 --- a/client/src/pages/ChangePassword.vue +++ b/client/src/pages/ChangePassword.vue @@ -1,6 +1,116 @@ - diff --git a/client/src/stores/auth.js b/client/src/stores/auth.js index e9d4e5af..600ce811 100644 --- a/client/src/stores/auth.js +++ b/client/src/stores/auth.js @@ -37,16 +37,22 @@ export const useAuthStore = defineStore('auth', { this.parseTokenAndSetData(response); if (this.returnUrl) router.push(this.returnUrl); } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, logout(isAuthRoute) { + const user = useUserStore(); + this.$patch({ userId: null, accessToken: { value: null, expires: Date.now() }, returnUrl: null, }); + + user.$patch({ + email: '', + }); if (isAuthRoute) router.push('/login'); }, @@ -60,7 +66,7 @@ export const useAuthStore = defineStore('auth', { ); return this.parseTokenAndSetData(response); } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, diff --git a/client/src/stores/user.js b/client/src/stores/user.js index 423a35ff..6b79a287 100644 --- a/client/src/stores/user.js +++ b/client/src/stores/user.js @@ -26,7 +26,7 @@ export const useUserStore = defineStore('user', { // Log users in after signup and return that response return await auth.login(email, password); } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, @@ -34,7 +34,7 @@ export const useUserStore = defineStore('user', { try { await axios.put(CHANGE_PASSWORD_URL, { email, password }, HEADERS); } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, @@ -42,13 +42,13 @@ export const useUserStore = defineStore('user', { try { await axios.get(REQUEST_PASSWORD_RESET_URL, { email }, HEADERS); } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, async resetPassword(email, password, token) { try { - const resetResponse = await axios.put( + const resetResponse = await axios.get( `${PASSWORD_RESET_URL}/${token}`, HEADERS, ); @@ -57,7 +57,7 @@ export const useUserStore = defineStore('user', { return await this.changePassword(email, password); } } catch (error) { - throw new Error(error.message); + throw new Error(error.response.data.message); } }, }, From aea7fb00c9a0aa24cade54aa133d5e9e50e4bbc2 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 10:38:26 -0500 Subject: [PATCH 43/73] feat(pages): add password reset route --- client/src/pages/LogIn.vue | 10 +- client/src/pages/ResetPassword.vue | 201 +++++++++++++++++++++++++++++ client/src/router.js | 15 ++- 3 files changed, 218 insertions(+), 8 deletions(-) create mode 100644 client/src/pages/ResetPassword.vue diff --git a/client/src/pages/LogIn.vue b/client/src/pages/LogIn.vue index fb2586ef..f15ee311 100644 --- a/client/src/pages/LogIn.vue +++ b/client/src/pages/LogIn.vue @@ -40,18 +40,20 @@ {{ getSubmitButtonCopy() }} -

    +

    {{ type === FORM_TYPES.login ? "Don't have an account?" : 'Already have an account?' }} -

    +

    + Forgot your password? + Click here to reset it +

    diff --git a/client/src/pages/ResetPassword.vue b/client/src/pages/ResetPassword.vue new file mode 100644 index 00000000..4ff325db --- /dev/null +++ b/client/src/pages/ResetPassword.vue @@ -0,0 +1,201 @@ + + + diff --git a/client/src/router.js b/client/src/router.js index 7cc62fbf..0408e0a0 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -1,10 +1,12 @@ import { createWebHistory, createRouter } from 'vue-router'; -import QuickSearchPage from '../src/pages/QuickSearchPage.vue'; -import SearchResultPage from '../src/pages/SearchResultPage.vue'; -import DataSourceStaticView from '../src/pages/DataSourceStaticView.vue'; +import { useAuthStore } from './stores/auth'; + import ChangePassword from './pages/ChangePassword.vue'; +import DataSourceStaticView from '../src/pages/DataSourceStaticView.vue'; import LogIn from './pages/LogIn.vue'; -import { useAuthStore } from './stores/auth'; +import QuickSearchPage from '../src/pages/QuickSearchPage.vue'; +import ResetPassword from './pages/ResetPassword.vue'; +import SearchResultPage from '../src/pages/SearchResultPage.vue'; export const PRIVATE_ROUTES = ['/change-password']; @@ -30,6 +32,11 @@ const routes = [ component: ChangePassword, name: 'ChangePassword', }, + { + path: '/reset-password/:token?', + component: ResetPassword, + name: 'ResetPassword', + }, ]; const router = createRouter({ From 240ed058d675304f83ad429dc2be373ba54953e6 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 10:52:11 -0500 Subject: [PATCH 44/73] refactor(store): log user back in on pw change --- client/src/pages/ChangePassword.vue | 2 -- client/src/stores/user.js | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/ChangePassword.vue b/client/src/pages/ChangePassword.vue index 4731c67b..13d14f4a 100644 --- a/client/src/pages/ChangePassword.vue +++ b/client/src/pages/ChangePassword.vue @@ -2,7 +2,6 @@

    Success

    Your password has been successfully updated

    - Click here to log in

    Change your password

    @@ -26,7 +25,6 @@ import { Button, Form } from 'pdap-design-system'; import { useUserStore } from '../stores/user'; import { ref } from 'vue'; -import { RouterLink } from 'vue-router'; // Constants const FORM_SCHEMA = [ diff --git a/client/src/stores/user.js b/client/src/stores/user.js index 6b79a287..43f324dc 100644 --- a/client/src/stores/user.js +++ b/client/src/stores/user.js @@ -31,8 +31,10 @@ export const useUserStore = defineStore('user', { }, async changePassword(email, password) { + const auth = useAuthStore(); try { await axios.put(CHANGE_PASSWORD_URL, { email, password }, HEADERS); + return await auth.login(email, password); } catch (error) { throw new Error(error.response.data.message); } From 9eb023bbe29ecc5bea0af90b58b00825848e9e1a Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 11:03:27 -0500 Subject: [PATCH 45/73] fix(stores): status code logic --- client/src/stores/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/stores/user.js b/client/src/stores/user.js index 43f324dc..13774da8 100644 --- a/client/src/stores/user.js +++ b/client/src/stores/user.js @@ -55,7 +55,7 @@ export const useUserStore = defineStore('user', { HEADERS, ); - if (400 < resetResponse.status > 200) { + if (400 > resetResponse.status > 200) { return await this.changePassword(email, password); } } catch (error) { From 821bbee726da70954fbeabe3c72898dc05b1b1ad Mon Sep 17 00:00:00 2001 From: Marty Bode Date: Mon, 26 Feb 2024 11:34:14 -0500 Subject: [PATCH 46/73] PR feedback changes --- resources/Login.py | 2 ++ resources/SearchTokens.py | 2 +- resources/User.py | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/Login.py b/resources/Login.py index f28bfa1a..af2715c8 100644 --- a/resources/Login.py +++ b/resources/Login.py @@ -32,6 +32,8 @@ def post(self): "data": token, } + return {"message": "Invalid email or password"}, 401 + except Exception as e: self.psycopg2_connection.rollback() print(str(e)) diff --git a/resources/SearchTokens.py b/resources/SearchTokens.py index 8ee99240..b65d8c95 100644 --- a/resources/SearchTokens.py +++ b/resources/SearchTokens.py @@ -11,7 +11,7 @@ sys.path.append("..") -BASE_URL = os.getenv("VITE_VUE_APP_BASE_URL") +BASE_URL = os.getenv("VITE_VUE_API_BASE_URL") class SearchTokens(Resource): diff --git a/resources/User.py b/resources/User.py index c2f2ca15..4b2ae434 100644 --- a/resources/User.py +++ b/resources/User.py @@ -2,6 +2,7 @@ from flask_restful import Resource from flask import request from middleware.user_queries import user_post_results +from middleware.security import api_required class User(Resource): @@ -29,6 +30,7 @@ def post(self): return {"message": e}, 500 # Endpoint for updating a user's password + @api_required def put(self): try: data = request.get_json() From 0e5c517aa810e41309ea34fb33a824f24938a316 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 26 Feb 2024 11:54:49 -0500 Subject: [PATCH 47/73] refactor: miscellaneous updates Error handling / clearing Auth header for change pw route --- client/src/pages/ChangePassword.vue | 4 ++-- client/src/pages/LogIn.vue | 12 +++++++++--- client/src/pages/ResetPassword.vue | 10 ++++++++-- client/src/stores/auth.js | 4 ++-- client/src/stores/user.js | 19 ++++++++++++++----- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/client/src/pages/ChangePassword.vue b/client/src/pages/ChangePassword.vue index 13d14f4a..2d2250f6 100644 --- a/client/src/pages/ChangePassword.vue +++ b/client/src/pages/ChangePassword.vue @@ -105,8 +105,8 @@ async function onSubmit(formValues) { await user.changePassword(user.email, password); success.value = true; - } catch (error) { - error.value = error; + } catch (err) { + error.value = err.message; } finally { loading.value = false; } diff --git a/client/src/pages/LogIn.vue b/client/src/pages/LogIn.vue index f15ee311..063f5f8a 100644 --- a/client/src/pages/LogIn.vue +++ b/client/src/pages/LogIn.vue @@ -60,7 +60,7 @@ diff --git a/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap b/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap index 743b0d94..184d8520 100644 --- a/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap +++ b/client/src/components/__tests__/__snapshots__/searchResultCard.test.js.snap @@ -28,7 +28,7 @@ exports[`SearchResultCard with all data > search result card exists with full da -->

    Time range

    2016–2017

    -

    Formats available

    +

    Formats available

    • [
    • '
    • @@ -58,7 +58,7 @@ exports[`SearchResultCard with coverage end but not start > search result card e

      Record type

      -

      Unknown

      +

      Unknown

      Agency

      Unknown

      @@ -82,8 +82,8 @@ exports[`SearchResultCard with coverage end but not start > search result card e -->

      Time range

      Unknown start–2016

      -

      Formats available

      -

      Unknown

      +

      Formats available

      +

      Unknown

      @@ -96,7 +96,7 @@ exports[`SearchResultCard with coverage start but not end > search result card e

      Record type

      -

      Unknown

      +

      Unknown

      Agency

      Unknown

      @@ -120,8 +120,8 @@ exports[`SearchResultCard with coverage start but not end > search result card e -->

      Time range

      2016–Unknown end

      -

      Formats available

      -

      Unknown

      +

      Formats available

      +

      Unknown

      @@ -134,7 +134,7 @@ exports[`SearchResultCard with missing data > search result card exists with mis

      Calls for Service for Cicero Police Department - IN

      Record type

      -

      Unknown

      +

      Unknown

      Agency

      Unknown

      @@ -157,9 +157,9 @@ exports[`SearchResultCard with missing data > search result card exists with mis
      -->

      Time range

      -

      Unknown

      -

      Formats available

      -

      Unknown

      +

      Unknown

      +

      Formats available

      +

      Unknown

      diff --git a/client/src/components/__tests__/searchResultCard.test.js b/client/src/components/__tests__/searchResultCard.test.js index 51ccfef2..99ddd99a 100644 --- a/client/src/components/__tests__/searchResultCard.test.js +++ b/client/src/components/__tests__/searchResultCard.test.js @@ -1,6 +1,6 @@ import SearchResultCard from '../SearchResultCard.vue'; import { mount } from '@vue/test-utils'; -import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { nextTick } from 'vue'; let wrapper; @@ -175,6 +175,10 @@ describe('SearchResultCard with missing data', () => { }); }); + afterEach(() => { + vi.resetAllMocks(); + }); + it('search result card exists with missing data', () => { expect(wrapper.find('[data-test="search-result-card"]').exists()).toBe( true, @@ -211,6 +215,16 @@ describe('SearchResultCard with missing data', () => { wrapper.find('[data-test="search-result-format-unknown"]').exists(), ).toBe(true); }); + + it('Does not call openSource when data is missing', async () => { + const button = wrapper.find( + '[data-test="search-result-visit-source-button"]', + ); + + await button.trigger('click'); + + expect(push).not.toHaveBeenCalled(); + }); }); // describe("SearchResultCard with municipality but not state", () => { diff --git a/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap b/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap index e6cf0c9c..a67e3ec2 100644 --- a/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap +++ b/client/src/pages/__tests__/__snapshots__/searchResultPage.test.js.snap @@ -46,7 +46,7 @@ exports[`SearchResultPage renders with data > Calls API and renders search resul -->

      Time range

      2016–Unknown end

      -

      Formats available

      +

      Formats available

      • [
      • ]
      • @@ -84,7 +84,7 @@ exports[`SearchResultPage renders with data > Calls API and renders search resul -->

        Time range

        2018–Unknown end

        -

        Formats available

        +

        Formats available

        • [
        • ]
        • @@ -122,7 +122,7 @@ exports[`SearchResultPage renders with data > Calls API and renders search resul -->

          Time range

          12/17/2018–Unknown end

          -

          Formats available

          +

          Formats available

          • [
          • '
          • From 15a266a7d17015e30567822c2076d5745aacce14 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Mon, 4 Mar 2024 16:34:19 -0500 Subject: [PATCH 71/73] test(pages): miscellaneous updates to static view --- client/src/pages/DataSourceStaticView.vue | 8 ++++---- .../src/pages/__tests__/dataSourceStaticView.test.js | 12 +++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/client/src/pages/DataSourceStaticView.vue b/client/src/pages/DataSourceStaticView.vue index c46c7329..7aba7760 100644 --- a/client/src/pages/DataSourceStaticView.vue +++ b/client/src/pages/DataSourceStaticView.vue @@ -62,7 +62,7 @@ v-for="item in dataSource[record.key]" :key="item" :class="record?.classNames" - :data-test="record['data-test']" + :data-test="record['data-test'] ?? 'data-source-item'" :href="dataSource[record.key]" :intent="record?.attributes?.intent" :target="record?.attributes?.target" @@ -87,7 +87,7 @@ " v-else-if="dataSource[record.key]" :class="record?.classNames" - :data-test="record['data-test']" + :data-test="record['data-test'] ?? 'data-source-item'" :href="dataSource[record.key]" :intent="record?.attributes?.intent" :target="record?.attributes?.target" @@ -106,7 +106,7 @@ :is="record.component ? record.component : 'p'" v-else-if="dataSource[record.renderIf]" :class="record?.classNames" - :data-test="record['data-test']" + :data-test="record['data-test'] ?? 'data-source-item'" :href="record?.href" :intent="record?.attributes?.intent" :target="record?.attributes?.target" @@ -168,7 +168,7 @@ export default { `https://web.archive.org/web/*/${this.dataSource.source_url}`, ); default: - return () => undefined; + return undefined; } }, async getDataSourceDetails() { diff --git a/client/src/pages/__tests__/dataSourceStaticView.test.js b/client/src/pages/__tests__/dataSourceStaticView.test.js index e2c16291..521ee523 100644 --- a/client/src/pages/__tests__/dataSourceStaticView.test.js +++ b/client/src/pages/__tests__/dataSourceStaticView.test.js @@ -76,9 +76,10 @@ describe('DataSourceStaticView', () => { }); it('Routes back to /search on Agency Name button click with correct parameters', async () => { - const button = wrapper.find('[data-test="agency-name-button"]'); + const button = wrapper.findComponent('[data-test="agency-name-button"]'); expect(button.exists()).toBe(true); + expect(button.props('intent')).toBe('tertiary'); const name = button.text(); @@ -105,6 +106,15 @@ describe('DataSourceStaticView', () => { ); }); + it('Does nothing on click of non-button item.', async () => { + const spy = vi.spyOn(window, 'open'); + const item = wrapper.find('[data-test="data-source-item"]'); + + item.trigger('click'); + + expect(spy).not.toHaveBeenLastCalledWith(); + }); + // it("renders correctly when there is no data", () => { // const wrapper = mount(DataSourceStaticView, { // data() { From fa5e17a779b204efee3922c55c305cbcdcba084c Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Tue, 5 Mar 2024 20:02:00 -0500 Subject: [PATCH 72/73] ci(pull): update env setting in API test --- .github/workflows/pull.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull.yaml b/.github/workflows/pull.yaml index 7f79ed80..4c76ca61 100644 --- a/.github/workflows/pull.yaml +++ b/.github/workflows/pull.yaml @@ -12,7 +12,7 @@ jobs: uses: styfle/cancel-workflow-action@0.11.0 with: access_token: ${{ secrets.GITHUB_TOKEN }} - + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: @@ -30,6 +30,8 @@ jobs: - uses: psf/black@stable test_api: + env: + SECRET_KEY: ${{ secrets.SECRET_KEY }} name: Test API runs-on: ubuntu-latest steps: @@ -37,7 +39,6 @@ jobs: - uses: actions/setup-python@v4 with: python-version: '3.11' - secret_key: ${{ secrets.SECRET_KEY }} - name: Install dependencies run: | From bc15f616c5ce3f6dd6bc979bd4bc98995b078652 Mon Sep 17 00:00:00 2001 From: Joshua Graber Date: Fri, 8 Mar 2024 13:51:09 -0500 Subject: [PATCH 73/73] docs(README): add local client base url --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c82e5f10..1e9d852a 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Reach out to contact@pdap.io or make noise in Discord if you'd like access to th DO_DATABASE_URL="postgres://data_sources_app:@db-postgresql-nyc3-38355-do-user-8463429-0.c.db.ondigitalocean.com:25060/defaultdb" VITE_VUE_API_BASE_URL="http://localhost:5000" +VITE_VUE_APP_BASE_URL="http://localhost:8888" ``` ``` @@ -63,6 +64,7 @@ VITE_VUE_API_BASE_URL="http://localhost:5000" export DO_DATABASE_URL=postgres://data_sources_app:@db-postgresql-nyc3-38355-do-user-8463429-0.c.db.ondigitalocean.com:25060/defaultdb export VITE_VUE_API_BASE_URL="http://localhost:5000" +export VITE_VUE_APP_BASE_URL="http://localhost:8888" ``` ### 6. Allow your IP address