From 4bd13c4cc668e8c7de78ae142fa213e0b73ae49d Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Thu, 24 Aug 2023 15:25:55 +0000 Subject: [PATCH 01/50] [ci skip] chore(release): 0.0.4 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0637aa1..ab3b7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.4 (2023-08-24) + ### 0.0.3 (2023-08-24) diff --git a/package-lock.json b/package-lock.json index 6bd4c6a..b8d4f7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.3", + "version": "0.0.4", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index e6d1774..be42716 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.3" + "version": "0.0.4" } From fca70d620032ec6d08445dea421e2578ec76db8d Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:08:09 -0500 Subject: [PATCH 02/50] docs: OpenApi Specification (#9) --- docs/spec.openapi.yaml | 602 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 602 insertions(+) create mode 100644 docs/spec.openapi.yaml diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml new file mode 100644 index 0000000..81f6342 --- /dev/null +++ b/docs/spec.openapi.yaml @@ -0,0 +1,602 @@ +openapi: 3.0.3 + +info: + title: Proxy Python + license: + name: MIT + url: https://github.com/hawks-atlanta/proxy-python/blob/main/LICENSE + version: 1.0.0 + +tags: + - name: Authentication + - name: Account + - name: Authorization + - name: Files + +paths: + /login: + post: + tags: + - Authentication + description: Authenticates to the server + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/credentials' + responses: + '200': + description: Login succeed + content: + application/json: + schema: + $ref: '#/components/schemas/authorization' + '401': + description: Invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /register: + post: + tags: + - Authentication + description: Register the new user in the service + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/credentials' + responses: + '201': + description: Registration succeed + content: + application/json: + schema: + $ref: '#/components/schemas/authorization' + '409': + description: Username already registered + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /challenge: + post: + tags: + - Authorization + description: Verifies the received token is still valid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/authorization' + responses: + '200': + description: Account token is still valid + content: + application/json: + schema: + $ref: '#/components/schemas/account' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /account/password: + patch: + tags: + - Account + description: Updates the username password + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/authorization' + - type: object + properties: + currentPassword: + type: string + newPassword: + type: string + responses: + '200': + description: Password updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files: + post: + tags: + - Files + description: Create a new file + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + fileName: + type: string + location: + type: string + file: + type: string + responses: + '201': + description: File created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /folders: + post: + tags: + - Files + description: Create a new folder + requestBody: + content: + application/json: + schema: + type: object + properties: + folderName: + type: string + location: + type: string #UUID + responses: + '201': + description: Folder created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files/shared: + get: + tags: + - Files + description: Gets the list of shared files + responses: + '200': + description: List of shared files obtained + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/fileDetailsShared' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /files/{fileId}/edit: + put: + tags: + - Files + description: Edit an existing file + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: query + name: newContent + required: true + schema: + type: string + responses: + '200': + description: File successfully edited + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /files/{fileId}: + delete: + tags: + - Files + description: Deletes an existing file + parameters: + - in: path + name: fileId + required: true + schema: + type: string + responses: + '200': + description: File successfully deleted + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + /files/{fileId}/move: + patch: + tags: + - Files + description: Move an existing file to a different location + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: query + name: newLocation + required: true + schema: + type: string + responses: + '200': + description: File moved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + + /files/list: + get: + tags: + - Files + description: List files in a given location + parameters: + - in: query + name: location + required: false + schema: + type: string + description: The location (UUID) to list files from + responses: + '200': + description: List of files in the location + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/fileDetails' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files/download/{fileUUID}: + get: + tags: + - Files + description: Download a file by UUID + parameters: + - in: path + name: fileUUID + required: true + schema: + type: string + description: The UUID of the file to download + responses: + '200': + description: File downloaded successfully + content: + application/octet-stream: + schema: + type: string + format: binary + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files/share: + post: + tags: + - Files + description: Share a file with a user + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/shareWithReq' + responses: + '200': + description: File shared successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files/unshare: + post: + tags: + - Files + description: Unshare a file with a user + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/shareWithReq' + responses: + '200': + description: File unshared successfully + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /files/sharedwithwho: + get: + tags: + - Files + description: Get the list of users the file is shared with + parameters: + - in: query + name: fileUUID + required: true + schema: + type: string + responses: + '200': + description: List of users the file is shared with + content: + application/json: + schema: + type: object + properties: + users: + type: array + items: + type: string + example: sulcud + + '401': + description: Not authorized + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '404': + description: File not found + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + '500': + description: Internal error + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + + +components: + schemas: + account: + type: object + properties: + uuid: + type: string + statusResponse: + type: object + properties: + msg: + type: string + succeed: + type: boolean + credentials: + type: object + properties: + username: + type: string + example: sulcud + password: + type: string + example: password + authorization: + type: object + properties: + jwt: + type: string + fileDetails: + type: object + properties: + fileName: + type: string + fileSize: + type: integer + isFile: + type: boolean + fileUUID: + type: string + fileDetailsShared: + type: object + properties: + fileName: + type: string + fileSize: + type: integer + isFile: + type: boolean + fileUUID: + type: string + ownerusername: + type: string + shareWithReq: + type: object + properties: + fileUUID: + type: string + otherUsername: + type: string \ No newline at end of file From 409ad31bdf2cb372b696637fb228acdd572478ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sun, 1 Oct 2023 15:41:40 -0500 Subject: [PATCH 03/50] chore: Update docker-compose (#12) * chore: Add missing env variables and services to docker-compose * chore: Use worker default env variables --- docker-compose.yaml | 51 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 99b65e8..d48e5d2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '3.1' +version: "3.1" services: # Gateway @@ -11,9 +11,24 @@ services: environment: METADATA_BASEURL: http://metadata:8080 AUTHENTICATION_BASEURL: http://authentication:8080 - # TODO: Worker + WORKER_HOST: worker + WORKER_PORT: 1099 + depends_on: + - worker + - metadata + - authentication # Microservices - # TODO: Add worker service + worker: + image: ghcr.io/hawks-atlanta/worker-java:latest + container_name: worker + restart: on-failure + ports: + - "127.0.0.1:1099:1099" + environment: + METADATA_BASEURL: http://metadata:8080/api/v1 + # Use the default directory that is created in the worker container + # VOLUME_BASE_PATH: /tmp + # VOLUME_COUNT: 3 metadata: image: ghcr.io/hawks-atlanta/metadata-scala:latest container_name: metadata @@ -21,11 +36,13 @@ services: ports: - "127.0.0.1:8082:8080" environment: - DATABASE_HOST: "postgres-db" + DATABASE_HOST: "metadata-db" DATABASE_PORT: "5432" DATABASE_NAME: "database" DATABASE_USER: "username" DATABASE_PASSWORD: "password" + depends_on: + - metadata-db authentication: image: ghcr.io/hawks-atlanta/authentication-go:latest container_name: authentication @@ -34,11 +51,13 @@ services: - "127.0.0.1:8083:8080" environment: DATABASE_ENGINE: postgres - DATABASE_DSN: "host=postgres-db user=username password=password dbname=database port=5432 sslmode=disable" - # Database - postgres-db: - image: postgres:latest - container_name: postgres-db + DATABASE_DSN: "host=authentication-db user=username password=password dbname=database port=5432 sslmode=disable" + depends_on: + - authentication-db + # Databases + authentication-db: + image: postgres:alpine3.18 + container_name: authentication-db restart: on-failure ports: - "127.0.0.1:5432:5432" @@ -46,7 +65,16 @@ services: - POSTGRES_USER=username - POSTGRES_PASSWORD=password - POSTGRES_DB=database - + metadata-db: + image: postgres:alpine3.18 + container_name: metadata-db + restart: on-failure + ports: + - "127.0.0.1:5434:5432" + environment: + - POSTGRES_USER=username + - POSTGRES_PASSWORD=password + - POSTGRES_DB=database postgres-admin: image: dpage/pgadmin4 container_name: postgres-admin @@ -56,4 +84,5 @@ services: - PGADMIN_DEFAULT_EMAIL=postgres@postgres.com - PGADMIN_DEFAULT_PASSWORD=postgres depends_on: - - postgres-db \ No newline at end of file + - authentication-db + - metadata-db From 22a84d250543bba959d0fe94587cef508a4109de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:04:48 -0500 Subject: [PATCH 04/50] ci: Linter and formatter (#14) * chore: Add recommended vscode extensions * chore(deps): Install black formatter and ruff linter * style: Lint and format files * docs: Add linting and formatting commands to README * ci: Add linter and style checks to integration pipeline --- .github/workflows/integration.yaml | 62 +++++++++++++++++++++++++++++ .github/workflows/testing.yaml | 30 -------------- .gitignore | 7 ++++ .vscode/extensions.json | 8 ++++ README.md | 30 +++++++++++++- pages/home/__init__.py | 5 ++- pages/home/_view.py | 3 +- pages/home/_view_test.py | 3 +- requirements.txt | Bin 616 -> 489 bytes version.py | 4 +- 10 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/integration.yaml delete mode 100644 .github/workflows/testing.yaml create mode 100644 .vscode/extensions.json diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml new file mode 100644 index 0000000..2144787 --- /dev/null +++ b/.github/workflows/integration.yaml @@ -0,0 +1,62 @@ +name: Integration + +on: + pull_request: + branches: ["dev"] + +jobs: + check-format: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Check format + run: black --check . + + check-linter: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Check linter + run: ruff check . + + test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Set up docker environment + run: docker-compose up -d + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Run tests + run: pytest + env: + PYTHONPATH: "." + + - name: Clean docker environment + run: docker compose down --rmi all -v --remove-orphans \ No newline at end of file diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml deleted file mode 100644 index c627fe7..0000000 --- a/.github/workflows/testing.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Test - -on: - pull_request: - branches: ["dev"] - -jobs: - test: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - - name: Set up docker environment - run: docker-compose up -d - - - uses: actions/setup-python@v4 - with: - python-version: '3.11' - cache: 'pip' - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Run tests - run: pytest - env: - PYTHONPATH: "." - - - name: Clean docker environment - run: docker compose down --rmi all -v --remove-orphans \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6127ff0..a9b9dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ +# Python files venv __pycache__ + +# Vscode files +.vscode/* +!.vscode/extensions.json + +# Pipelines files .coverage node_modules coverage.xml \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..be3e2f8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-python.black-formatter", + "charliermarsh.ruff", + "usernamehw.errorlens" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index f850d17..aa2ec4f 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,34 @@ docker compose up -d coverage run -m pytest ``` +### Formatting and linting + +If you installed the recommended extensions for VSCode, you may have the formatting and linting configured out of the box. + +Additionally, you may need to configure `black` to format `python` files with the following steps: + +- Open the command palette (Ctrl + Shift + P) +- Search Format Document With... +- Search Configure Default Formatter... +- Select "Black Formatter" + +You can also run the following commands to format and lint the code from the console: + +```bash +# Check format +black --check . + +# Format all python files +black . + +# Check lint +ruff check . + +# Fix lint (if possible) +ruff check --fix . +``` + ## Coverage | [![circle](https://codecov.io/gh/hawks-atlanta/proxy-python/graphs/sunburst.svg?token=JODBEVCYCF)](https://app.codecov.io/gh/hawks-atlanta/proxy-python) | [![square](https://codecov.io/gh/hawks-atlanta/proxy-python/graphs/tree.svg?token=JODBEVCYCF)](https://app.codecov.io/gh/hawks-atlanta/proxy-python) | -| ------------------------------------------------------------ | ------------------------------------------------------------ | - +| -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/pages/home/__init__.py b/pages/home/__init__.py index c02413e..fe56a2b 100644 --- a/pages/home/__init__.py +++ b/pages/home/__init__.py @@ -1,5 +1,6 @@ import flask -from ._view import * +from ._view import view + home = flask.Blueprint("home", __name__) -home.register_blueprint(view) \ No newline at end of file +home.register_blueprint(view) diff --git a/pages/home/_view.py b/pages/home/_view.py index 9e3e92a..5dfe67e 100644 --- a/pages/home/_view.py +++ b/pages/home/_view.py @@ -2,6 +2,7 @@ view = flask.Blueprint("home", __name__) + @view.route("/", methods=["GET"]) def index(): - return flask.render_template("index.html") \ No newline at end of file + return flask.render_template("index.html") diff --git a/pages/home/_view_test.py b/pages/home/_view_test.py index 59d3e45..2335a78 100644 --- a/pages/home/_view_test.py +++ b/pages/home/_view_test.py @@ -1,5 +1,6 @@ from main import app + def test_view() -> None: response = app.test_client().get("/") - assert response.status_code == 200 \ No newline at end of file + assert response.status_code == 200 diff --git a/requirements.txt b/requirements.txt index c119a06e0888aeafbe726f2d0c40dd8461ca3443..807163b1df4d160d5dbedd76d8703fead125dd62 100644 GIT binary patch literal 489 zcmX9*v3BGj4D9)r)dPEz%@(vN@?NSdQfo1G6l357&c*xnCvD0>Gn&zWt#hHt`vKe& zyqH_UunHRC3QiE^HL~{(o?$Q{a;KSOY=v-vL7qO5TkNM?c*8_@C66$GH6f91dI1L) z6G|$K8+vcy49|kUBjXaizrZNuiBi&;Y&EY5n??{Y8k(|*MmQVF6iS|H(UBDmwZh%| zCva*x$YpQJaFX|p@)r&&SCsI6oXYJhhJLDCehxkBD$;l4b+3PLmN+UL%@zXQ z1X(%Kn(e5^DgN}B8Q~kG1AM>obsI literal 616 zcmYL{%}&EW41|3~;!!|pQng&TazNt5f%CS|5NMN-CP?t`z&Ca_DBAvb#^V`pet&j$ zw8mO{w-+1imS<(xw&9d^X&1Kl`UvO15nfussbFlKm3gn0P&mUM?Fz+?eDw9W=#@%x9We^fHD|LhK3zFk&9@lxq#2=qheE$Pin(N1?ShnB_7@2?KqgKHzP# z-X8gVm@{aaSmCn=(T0>!YT1!IuiSG_%87byUp^%!WVb!l5#=+Vc|+P^Y-j-apgZvY j43#?w&V Date: Wed, 4 Oct 2023 18:39:07 -0500 Subject: [PATCH 05/50] feat: endpoint login (#15) --- docs/spec.openapi.yaml | 247 +++++++++--------- main.py | 8 +- pages/home/__init__.py | 6 - pages/home/_view.py | 8 - pages/home/_view_test.py | 6 - requirements.txt | Bin 489 -> 1062 bytes src/config/environment.py | 5 + src/config/soap_client.py | 5 + .../_authentication_controllers.py | 31 +++ src/views/__init__.py | 9 + src/views/_authentication_views.py | 9 + src/views/_authentication_views_test.py | 43 +++ templates/index.html | 1 - 13 files changed, 228 insertions(+), 150 deletions(-) delete mode 100644 pages/home/__init__.py delete mode 100644 pages/home/_view.py delete mode 100644 pages/home/_view_test.py create mode 100644 src/config/environment.py create mode 100644 src/config/soap_client.py create mode 100644 src/controllers/_authentication_controllers.py create mode 100644 src/views/__init__.py create mode 100644 src/views/_authentication_views.py create mode 100644 src/views/_authentication_views_test.py delete mode 100644 templates/index.html diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 81f6342..830f42a 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -23,26 +23,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/credentials' + $ref: "#/components/schemas/credentials" responses: - '200': + "200": description: Login succeed content: application/json: schema: - $ref: '#/components/schemas/authorization' - '401': + $ref: "#/components/schemas/authorization" + "401": description: Invalid credentials content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /register: post: tags: @@ -52,26 +52,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/credentials' + $ref: "#/components/schemas/credentials" responses: - '201': + "201": description: Registration succeed content: application/json: schema: - $ref: '#/components/schemas/authorization' - '409': + $ref: "#/components/schemas/authorization" + "409": description: Username already registered content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /challenge: post: tags: @@ -81,26 +81,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/authorization' + $ref: "#/components/schemas/authorization" responses: - '200': + "200": description: Account token is still valid content: application/json: schema: - $ref: '#/components/schemas/account' - '401': + $ref: "#/components/schemas/account" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /account/password: patch: tags: @@ -111,7 +111,7 @@ paths: application/json: schema: allOf: - - $ref: '#/components/schemas/authorization' + - $ref: "#/components/schemas/authorization" - type: object properties: currentPassword: @@ -119,24 +119,24 @@ paths: newPassword: type: string responses: - '200': + "200": description: Password updated successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files: post: @@ -152,28 +152,28 @@ paths: fileName: type: string location: - type: string + type: string file: - type: string + type: string responses: - '201': + "201": description: File created successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /folders: post: @@ -189,26 +189,26 @@ paths: folderName: type: string location: - type: string #UUID + type: string #UUID responses: - '201': + "201": description: Folder created successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files/shared: get: @@ -216,26 +216,26 @@ paths: - Files description: Gets the list of shared files responses: - '200': + "200": description: List of shared files obtained content: application/json: schema: type: array items: - $ref: '#/components/schemas/fileDetailsShared' - '401': + $ref: "#/components/schemas/fileDetailsShared" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files/{fileId}/edit: put: tags: @@ -253,24 +253,24 @@ paths: schema: type: string responses: - '200': + "200": description: File successfully edited content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files/{fileId}: delete: tags: @@ -283,24 +283,24 @@ paths: schema: type: string responses: - '200': + "200": description: File successfully deleted content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': - description: Internal error + $ref: "#/components/schemas/statusResponse" + "500": + description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files/{fileId}/move: patch: tags: @@ -318,26 +318,25 @@ paths: schema: type: string responses: - '200': + "200": description: File moved successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - - + $ref: "#/components/schemas/statusResponse" + /files/list: get: tags: @@ -348,30 +347,30 @@ paths: name: location required: false schema: - type: string + type: string description: The location (UUID) to list files from responses: - '200': + "200": description: List of files in the location content: application/json: schema: type: array items: - $ref: '#/components/schemas/fileDetails' - '401': + $ref: "#/components/schemas/fileDetails" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - + $ref: "#/components/schemas/statusResponse" + /files/download/{fileUUID}: get: tags: @@ -385,31 +384,31 @@ paths: type: string description: The UUID of the file to download responses: - '200': + "200": description: File downloaded successfully content: - application/octet-stream: + application/octet-stream: schema: - type: string - format: binary - '401': + type: string + format: binary + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '404': + $ref: "#/components/schemas/statusResponse" + "404": description: File not found content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' + $ref: "#/components/schemas/statusResponse" /files/share: post: @@ -420,39 +419,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/shareWithReq' + $ref: "#/components/schemas/shareWithReq" responses: - '200': + "200": description: File shared successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '400': + $ref: "#/components/schemas/statusResponse" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '404': + $ref: "#/components/schemas/statusResponse" + "404": description: File not found content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - + $ref: "#/components/schemas/statusResponse" + /files/unshare: post: tags: @@ -462,39 +461,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/shareWithReq' + $ref: "#/components/schemas/shareWithReq" responses: - '200': + "200": description: File unshared successfully content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '400': + $ref: "#/components/schemas/statusResponse" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '401': + $ref: "#/components/schemas/statusResponse" + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '404': + $ref: "#/components/schemas/statusResponse" + "404": description: File not found content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - + $ref: "#/components/schemas/statusResponse" + /files/sharedwithwho: get: tags: @@ -505,9 +504,9 @@ paths: name: fileUUID required: true schema: - type: string + type: string responses: - '200': + "200": description: List of users the file is shared with content: application/json: @@ -520,26 +519,24 @@ paths: type: string example: sulcud - '401': + "401": description: Not authorized content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '404': + $ref: "#/components/schemas/statusResponse" + "404": description: File not found content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - '500': + $ref: "#/components/schemas/statusResponse" + "500": description: Internal error content: application/json: schema: - $ref: '#/components/schemas/statusResponse' - - + $ref: "#/components/schemas/statusResponse" components: schemas: @@ -553,8 +550,6 @@ components: properties: msg: type: string - succeed: - type: boolean credentials: type: object properties: @@ -592,11 +587,11 @@ components: fileUUID: type: string ownerusername: - type: string + type: string shareWithReq: type: object properties: fileUUID: type: string otherUsername: - type: string \ No newline at end of file + type: string diff --git a/main.py b/main.py index 33b5539..c8edc98 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import flask -import pages.home +from src.views import views -app = flask.Flask(__name__, template_folder="templates") +app = flask.Flask(__name__) +app.register_blueprint(views) -app.register_blueprint(pages.home.home) +if __name__ == "__main__": + app.run(debug=True) diff --git a/pages/home/__init__.py b/pages/home/__init__.py deleted file mode 100644 index fe56a2b..0000000 --- a/pages/home/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import flask -from ._view import view - - -home = flask.Blueprint("home", __name__) -home.register_blueprint(view) diff --git a/pages/home/_view.py b/pages/home/_view.py deleted file mode 100644 index 5dfe67e..0000000 --- a/pages/home/_view.py +++ /dev/null @@ -1,8 +0,0 @@ -import flask - -view = flask.Blueprint("home", __name__) - - -@view.route("/", methods=["GET"]) -def index(): - return flask.render_template("index.html") diff --git a/pages/home/_view_test.py b/pages/home/_view_test.py deleted file mode 100644 index 2335a78..0000000 --- a/pages/home/_view_test.py +++ /dev/null @@ -1,6 +0,0 @@ -from main import app - - -def test_view() -> None: - response = app.test_client().get("/") - assert response.status_code == 200 diff --git a/requirements.txt b/requirements.txt index 807163b1df4d160d5dbedd76d8703fead125dd62..c457864db5875772bf6ba6dedc16a223a05d7ee5 100644 GIT binary patch literal 1062 zcmY+D%TmHn5JYEfm7k(T0C_Cjy0FTX3)cYxL>`F{6#01d^h_>brQ+lEbWcyu&F@cc zduwcGm6bNLN1m?TTV@aZ9NDei*u>VB+tgMq+5;JTO+riCf)=QVpMec{yUw2ad4u|# zH}pGTQW<(xs$0@!hnoUFcPLi;x8ZCJ?{rodPYye+y3pip#JRc;TdaaBmE3jIy93(i z9h@sZT{*V}zf~bm>Jj=g)Ym+T8?H6?6Q{~9r4p>3>nh1Ds90k3+taOkt-zGU!tt0T z^(#@7kS<3!j5z-@VLjR z5{W6)Q=F);QPOU;9nF&xi)pk4RW!DQUHcSiPubP9wQs7yaoi~FRQD`xC;QiN&4de{ zO9q$QChu~sFGg%lrrQ*5cOW0I1HWvg=U!6H0UvW`$DG>9mGn&zWt#hHt`vKe& zyqH_UunHRC3QiE^HL~{(o?$Q{a;KSOY=v-vL7qO5TkNM?c*8_@C66$GH6f91dI1L) z6G|$K8+vcy49|kUBjXaizrZNuiBi&;Y&EY5n??{Y8k(|*MmQVF6iS|H(UBDmwZh%| zCva*x$YpQJaFX|p@)r&&SCsI6oXYJhhJLDCehxkBD$;l4b+3PLmN+UL%@zXQ z1X(%Kn(e5^DgN}B8Q~kG1AM>obsI diff --git a/src/config/environment.py b/src/config/environment.py new file mode 100644 index 0000000..5f7962d --- /dev/null +++ b/src/config/environment.py @@ -0,0 +1,5 @@ +import os + +variables = { + "GATEWAY_BASEURL": os.getenv("GATEWAY_BASEURL", "http://localhost:8080"), +} diff --git a/src/config/soap_client.py b/src/config/soap_client.py new file mode 100644 index 0000000..5feb89c --- /dev/null +++ b/src/config/soap_client.py @@ -0,0 +1,5 @@ +from zeep import Client +from .environment import variables + +soap_wsdl = f"{variables['GATEWAY_BASEURL']}/service?wsdl" +soap_client = Client(soap_wsdl) diff --git a/src/controllers/_authentication_controllers.py b/src/controllers/_authentication_controllers.py new file mode 100644 index 0000000..d209391 --- /dev/null +++ b/src/controllers/_authentication_controllers.py @@ -0,0 +1,31 @@ +from flask import request +from src.config.soap_client import soap_client + + +def login_handler(): + try: + # Get JSON data from the request + data = request.json + + if not data: + return {"msg": "No JSON data provided in the request"}, 400 + + username = data.get("username") + password = data.get("password") + + if not username or not password: + return {"msg": "Required fields are missing in JSON data"}, 400 + + result = soap_client.service.auth_login( + {"username": username, "password": password} + ) + + if result.auth is not None: + jwt = result.auth.token + return {"msg": "Login successful", "jwt": jwt}, 200 + + return {"msg": "Invalid credentials"}, 401 + + except Exception as e: + print("SOAP Error:", str(e)) + return {"msg": "Internal error", "error": str(e)}, 500 diff --git a/src/views/__init__.py b/src/views/__init__.py new file mode 100644 index 0000000..127bccd --- /dev/null +++ b/src/views/__init__.py @@ -0,0 +1,9 @@ +import flask +from ._authentication_views import views as authentication_views + + +# NOTE: Register all views / routes using the following blueprint +views = flask.Blueprint("views", __name__) + + +views.register_blueprint(authentication_views) diff --git a/src/views/_authentication_views.py b/src/views/_authentication_views.py new file mode 100644 index 0000000..cebb89c --- /dev/null +++ b/src/views/_authentication_views.py @@ -0,0 +1,9 @@ +import flask +from src.controllers import _authentication_controllers + +views = flask.Blueprint("authentication", __name__) + + +@views.route("/auth_login", methods=["POST"]) +def auth_login(): + return _authentication_controllers.login_handler() diff --git a/src/views/_authentication_views_test.py b/src/views/_authentication_views_test.py new file mode 100644 index 0000000..bfc3f8e --- /dev/null +++ b/src/views/_authentication_views_test.py @@ -0,0 +1,43 @@ +import json +from main import app +from config.soap_client import soap_client + + +def test_auth_login_successful() -> None: + registration_data = {"username": "miguel1", "password": "miguel1"} + + registration_result = soap_client.service.account_register(registration_data) + + assert registration_result.error is False + + login_data = {"username": "miguel1", "password": "miguel1"} + + response = app.test_client().post("/auth_login", json=login_data) + + assert response.status_code == 200 + + assert json.loads(response.data)["msg"] == "Login successful" + + assert "jwt" in json.loads(response.data) + + +def test_auth_login_invalid_credentials() -> None: + data = {"username": "miguel", "password": "miguel"} + + response = app.test_client().post("/auth_login", json=data) + + assert response.status_code == 401 + + assert json.loads(response.data)["msg"] == "Invalid credentials" + + +def test_auth_login_missing_fields() -> None: + data = {"username": "miguel"} + + response = app.test_client().post("/auth_login", json=data) + + assert response.status_code == 400 + + assert ( + json.loads(response.data)["msg"] == "Required fields are missing in JSON data" + ) # noqa: E501 diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 6116828..0000000 --- a/templates/index.html +++ /dev/null @@ -1 +0,0 @@ -

Hello world

\ No newline at end of file From e0e1ea1c2dbc5ea9604576e950376b506ee8bec5 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Wed, 4 Oct 2023 23:44:10 +0000 Subject: [PATCH 06/50] [ci skip] chore(release): 0.0.5 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab3b7b6..949b754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.5 (2023-10-04) + + +### Features + +* endpoint login ([#15](https://github.com/hawks-atlanta/proxy-python/issues/15)) ([4921ca1](https://github.com/hawks-atlanta/proxy-python/commit/4921ca11b886d950d7811f67ac573b7a9adc6831)) + ### 0.0.4 (2023-08-24) ### 0.0.3 (2023-08-24) diff --git a/package-lock.json b/package-lock.json index b8d4f7f..466abc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.4", + "version": "0.0.5", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index be42716..6c38fbd 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.4" + "version": "0.0.5" } From ee78fb6864d0f6616464439c5c177884e514cf29 Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Sun, 8 Oct 2023 18:28:25 -0500 Subject: [PATCH 07/50] feat: endpoint challenge (#33) --- requirements.txt | Bin 1062 -> 1066 bytes src/config/soap_client.py | 2 +- .../_authentication_controllers.py | 30 +++++++++ src/views/_authentication_views.py | 5 ++ src/views/_authentication_views_test.py | 58 +++++++++++++++++- 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index c457864db5875772bf6ba6dedc16a223a05d7ee5..7e74095a548afcc8937527fcfa06170b5b3f7e72 100644 GIT binary patch delta 12 TcmZ3+v5I4Z8Vd_A0~Z4T78U|l delta 7 OcmZ3*v5aGb8Vdjln*uuk diff --git a/src/config/soap_client.py b/src/config/soap_client.py index 5feb89c..8235b0d 100644 --- a/src/config/soap_client.py +++ b/src/config/soap_client.py @@ -1,5 +1,5 @@ from zeep import Client from .environment import variables -soap_wsdl = f"{variables['GATEWAY_BASEURL']}/service?wsdl" +soap_wsdl = f"{variables['GATEWAY_BASEURL']}/gw/service?wsdl" soap_client = Client(soap_wsdl) diff --git a/src/controllers/_authentication_controllers.py b/src/controllers/_authentication_controllers.py index d209391..1807967 100644 --- a/src/controllers/_authentication_controllers.py +++ b/src/controllers/_authentication_controllers.py @@ -29,3 +29,33 @@ def login_handler(): except Exception as e: print("SOAP Error:", str(e)) return {"msg": "Internal error", "error": str(e)}, 500 + + +def challenge(): + try: + data = request.json + + if not data: + return {"msg": "No JSON data provided in the request"}, 400 + + token = data.get("jwt") + + if not token: + return {"msg": "Token is missing in JSON data"}, 400 + + response = soap_client.service.auth_refresh({"token": token}) + + print(response) + + if hasattr(response, "auth") and hasattr(response.auth, "token"): + jwt = response.auth.token + return { + "msg": "JWT refreshed successfully", + "jwt": jwt, + }, 200 + else: + return {"msg": response.msg}, response.code + + except Exception as e: + print("Error:", str(e)) + return {"msg": "Internal error: " + str(e)}, 500 diff --git a/src/views/_authentication_views.py b/src/views/_authentication_views.py index cebb89c..618e056 100644 --- a/src/views/_authentication_views.py +++ b/src/views/_authentication_views.py @@ -7,3 +7,8 @@ @views.route("/auth_login", methods=["POST"]) def auth_login(): return _authentication_controllers.login_handler() + + +@views.route("/auth_refresh", methods=["POST"]) +def auth_refresh(): + return _authentication_controllers.challenge() diff --git a/src/views/_authentication_views_test.py b/src/views/_authentication_views_test.py index bfc3f8e..e12914a 100644 --- a/src/views/_authentication_views_test.py +++ b/src/views/_authentication_views_test.py @@ -4,13 +4,13 @@ def test_auth_login_successful() -> None: - registration_data = {"username": "miguel1", "password": "miguel1"} + registration_data = {"username": "miguel12", "password": "miguel12"} registration_result = soap_client.service.account_register(registration_data) assert registration_result.error is False - login_data = {"username": "miguel1", "password": "miguel1"} + login_data = {"username": "miguel12", "password": "miguel12"} response = app.test_client().post("/auth_login", json=login_data) @@ -40,4 +40,56 @@ def test_auth_login_missing_fields() -> None: assert ( json.loads(response.data)["msg"] == "Required fields are missing in JSON data" - ) # noqa: E501 + ) + + +def test_auth_refresh_missing_token() -> None: + # Simulate a request without a token + data = {"jwt": ""} + + response = app.test_client().post("/auth_refresh", json=data) + + assert response.status_code == 400 + + assert json.loads(response.data)["msg"] == "Token is missing in JSON data" + + +def test_no_valid() -> None: + registration_data = {"username": "pedro19", "password": "pedro19"} + registration_result = soap_client.service.account_register(registration_data) + assert registration_result.error is False + + login_data = {"username": "pedro19", "password": "pedro19"} + login_response = app.test_client().post("/auth_login", json=login_data) + assert login_response.status_code == 200 + jwt = json.loads(login_response.data)["jwt"] + + data = {"jwt": jwt + "a"} + response = app.test_client().post("/auth_refresh", json=data) + + assert response.status_code == 401 + + response_data = json.loads(response.data) + assert "msg" in response_data + assert response_data["msg"] != "" + + +def test_challenge_valid_token() -> None: + registration_data = {"username": "gloria1", "password": "gloria1"} + registration_result = soap_client.service.account_register(registration_data) + assert registration_result.error is False + + login_data = {"username": "gloria1", "password": "gloria1"} + login_response = app.test_client().post("/auth_login", json=login_data) + assert login_response.status_code == 200 + jwt = json.loads(login_response.data)["jwt"] + + data = {"jwt": jwt} + response = app.test_client().post("/auth_refresh", json=data) + + assert response.status_code == 200 + + # Verificar si la respuesta contiene un token JWT válido + response_data = json.loads(response.data) + assert "jwt" in response_data + assert response_data["jwt"] != "" From 8053b9971ff5621993b1b3abc779fecb47dccd76 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sun, 8 Oct 2023 23:30:55 +0000 Subject: [PATCH 08/50] [ci skip] chore(release): 0.0.6 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 949b754..85afce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.6 (2023-10-08) + + +### Features + +* endpoint challenge ([#33](https://github.com/hawks-atlanta/proxy-python/issues/33)) ([ee78fb6](https://github.com/hawks-atlanta/proxy-python/commit/ee78fb6864d0f6616464439c5c177884e514cf29)) + ### 0.0.5 (2023-10-04) diff --git a/package-lock.json b/package-lock.json index 466abc8..dfe5b9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.5", + "version": "0.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.5", + "version": "0.0.6", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 6c38fbd..6697cfe 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.5" + "version": "0.0.6" } From 556edf93e8b1033ca0e559f1aa432e38bb3c677e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:13:31 -0500 Subject: [PATCH 09/50] refactor: Update endpoints (#37) * refactor: Create reusable middleware to get and inject the jwt * refactor: Update login and challenge endpoints * docs(openapi): Update login and refresh endpoints in spec * refactor: Update login and refresh endpoints according to the spec * test: Update tests using random data * refactor: Remove unreachable block * chore(deps): Update dependencies --- docs/spec.openapi.yaml | 35 ++++-- requirements.txt | Bin 1066 -> 669 bytes .../_authentication_controllers.py | 33 ++---- src/lib/faker.py | 13 +++ src/middlewares/auth_middlewares.py | 18 ++++ src/views/_authentication_views.py | 10 +- src/views/_authentication_views_test.py | 102 +++++++++--------- 7 files changed, 121 insertions(+), 90 deletions(-) create mode 100644 src/lib/faker.py create mode 100644 src/middlewares/auth_middlewares.py diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 830f42a..7f150bf 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -14,7 +14,7 @@ tags: - name: Files paths: - /login: + /auth/login: post: tags: - Authentication @@ -72,25 +72,35 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /challenge: + /auth/refresh: post: tags: - Authorization + security: + - bearer: [] description: Verifies the received token is still valid - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/authorization" responses: "200": - description: Account token is still valid + description: The current token is valid and a new token was obtained successfully. + content: + application/json: + schema: + type: object + properties: + msg: + type: string + example: "Login successfull" + token: + type: string + example: "{jwt}" + "400": + description: JSON body wasn't given or was not valid. content: application/json: schema: - $ref: "#/components/schemas/account" + $ref: "#/components/schemas/statusResponse" "401": - description: Unauthorized + description: No authorization header was given. content: application/json: schema: @@ -539,6 +549,11 @@ paths: $ref: "#/components/schemas/statusResponse" components: + securitySchemes: + bearer: + type: http + scheme: bearer + bearerFormat: JWT schemas: account: type: object diff --git a/requirements.txt b/requirements.txt index 7e74095a548afcc8937527fcfa06170b5b3f7e72..4661a8ce3677abd46077e3a5bf30310f3923aaf8 100644 GIT binary patch literal 669 zcmYLHL5`d-5WM>@SQyU^GY4Pyuu`N*S!vG(4AYCTiETCmuTM3rC^yR8T~%F;%_T(| zjA^8LkvCyWNf}5>C2P@n+s`MuO9ez>cF7neud)%nONB;4xkh^{8$3O1%C1x)rp0Bb;$lGJD zV)yC#QmrXIci#DqQU6@v?1FDCe%Lf`>n}S(hDo$o`rdY@Xp7!uU-!yAiwl`zfD#hL z^NL9AM1j3R4Y)k=>U#F7SbX)ij0kWbz64s%DZfqAX@fWTxxan9v+9ir_u?|PYmw!N z9x+tB{Zxup&H3!3nh`)|Hl%e@0^Fw_a12* zNA_IH=B`AhCl}NeJS8rECNyFp1S7WOJ$0I`;NIfHrTK8%c`Lr~pJdmQ6+d7t4(C=* Hm`F{6#01d^h_>brQ+lEbWcyu&F@cc zduwcGm6bNLN1m?TTV@aZ9NDei*u>VB+tgMq+5;JTO+riCf)=QVpMec{yUw2ad4u|# zH}pGTQW<(xs$0@!hnoUFcPLi;x8ZCJ?{rodPYye+y3pip#JRc;TdaaBmE3jIy93(i z9h@sZT{*V}zf~bm>Jj=g)Ym+T8?H6?6Q{~9r4p>3>nh1Ds90k3+taOkt-zGU!tt0T z^(#@7kS<3!j5z-@VLjR z5{W6)Q=F);QPOU;9nF&xi)pk4RW!DQUHcSiPubP9wQs7yaoi~FRQD`xC;QiN&4de{ zO9q$QChu~sFGg%lrrQ*5cOW0I1HWvg=U!6H0UvW`$DG>9m", str(e)) return {"msg": "Internal error", "error": str(e)}, 500 -def challenge(): +def challenge_handler(token): try: - data = request.json - - if not data: - return {"msg": "No JSON data provided in the request"}, 400 - - token = data.get("jwt") - - if not token: - return {"msg": "Token is missing in JSON data"}, 400 - response = soap_client.service.auth_refresh({"token": token}) - - print(response) - - if hasattr(response, "auth") and hasattr(response.auth, "token"): - jwt = response.auth.token + if hasattr(response.auth, "token"): + refreshed_token = response.auth.token return { "msg": "JWT refreshed successfully", - "jwt": jwt, + "token": refreshed_token, }, 200 else: return {"msg": response.msg}, response.code except Exception as e: - print("Error:", str(e)) + print("[Exception] challenge ->", str(e)) return {"msg": "Internal error: " + str(e)}, 500 diff --git a/src/lib/faker.py b/src/lib/faker.py new file mode 100644 index 0000000..86fafa7 --- /dev/null +++ b/src/lib/faker.py @@ -0,0 +1,13 @@ +from faker import Faker + +# Unique faker instance +_fake = Faker() + + +# Helper functions +def fake_username() -> str: + return _fake.unique.user_name() + + +def fake_password() -> str: + return _fake.password() diff --git a/src/middlewares/auth_middlewares.py b/src/middlewares/auth_middlewares.py new file mode 100644 index 0000000..dcb65a7 --- /dev/null +++ b/src/middlewares/auth_middlewares.py @@ -0,0 +1,18 @@ +from functools import wraps +from flask import request, abort, jsonify, make_response + + +def token_required(f): + @wraps(f) + def decorated(*args, **kwargs): + # Check the token is present as a header + token = None + if "Authorization" in request.headers: + token = request.headers["Authorization"].split("Bearer ")[1] + if not token or token == "": + abort(make_response(jsonify({"msg": "Token is missing"}), 401)) + + # Return the function with the token as a parameter + return f(token, *args, **kwargs) + + return decorated diff --git a/src/views/_authentication_views.py b/src/views/_authentication_views.py index 618e056..eb278bc 100644 --- a/src/views/_authentication_views.py +++ b/src/views/_authentication_views.py @@ -1,14 +1,16 @@ import flask from src.controllers import _authentication_controllers +from src.middlewares import auth_middlewares views = flask.Blueprint("authentication", __name__) -@views.route("/auth_login", methods=["POST"]) +@views.route("/auth/login", methods=["POST"]) def auth_login(): return _authentication_controllers.login_handler() -@views.route("/auth_refresh", methods=["POST"]) -def auth_refresh(): - return _authentication_controllers.challenge() +@views.route("/auth/refresh", methods=["POST"]) +@auth_middlewares.token_required +def auth_refresh(token): + return _authentication_controllers.challenge_handler(token) diff --git a/src/views/_authentication_views_test.py b/src/views/_authentication_views_test.py index e12914a..5b836a8 100644 --- a/src/views/_authentication_views_test.py +++ b/src/views/_authentication_views_test.py @@ -1,95 +1,97 @@ import json from main import app from config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password def test_auth_login_successful() -> None: - registration_data = {"username": "miguel12", "password": "miguel12"} + test_data = {"username": fake_username(), "password": fake_password()} + registration_data = { + "username": test_data["username"], + "password": test_data["password"], + } registration_result = soap_client.service.account_register(registration_data) - assert registration_result.error is False - login_data = {"username": "miguel12", "password": "miguel12"} - - response = app.test_client().post("/auth_login", json=login_data) - + login_data = {"username": test_data["username"], "password": test_data["password"]} + response = app.test_client().post("/auth/login", json=login_data) assert response.status_code == 200 - assert json.loads(response.data)["msg"] == "Login successful" - - assert "jwt" in json.loads(response.data) + json_response = json.loads(response.data) + assert json_response["msg"] != "" + assert json_response["token"] != "" def test_auth_login_invalid_credentials() -> None: - data = {"username": "miguel", "password": "miguel"} - - response = app.test_client().post("/auth_login", json=data) + data = {"username": "unexistent", "password": "password"} + response = app.test_client().post("/auth/login", json=data) + json_response = json.loads(response.data) assert response.status_code == 401 - - assert json.loads(response.data)["msg"] == "Invalid credentials" + assert json_response["msg"] == "Invalid credentials" def test_auth_login_missing_fields() -> None: data = {"username": "miguel"} - - response = app.test_client().post("/auth_login", json=data) - + response = app.test_client().post("/auth/login", json=data) assert response.status_code == 400 - assert ( - json.loads(response.data)["msg"] == "Required fields are missing in JSON data" - ) + data = {"password": "miguel"} + response = app.test_client().post("/auth/login", json=data) + assert response.status_code == 400 def test_auth_refresh_missing_token() -> None: - # Simulate a request without a token - data = {"jwt": ""} - - response = app.test_client().post("/auth_refresh", json=data) - - assert response.status_code == 400 - - assert json.loads(response.data)["msg"] == "Token is missing in JSON data" + response = app.test_client().post("/auth/refresh") + assert response.status_code == 401 + assert json.loads(response.data)["msg"] == "Token is missing" -def test_no_valid() -> None: - registration_data = {"username": "pedro19", "password": "pedro19"} +def test_auth_refresh_not_valid_token() -> None: + test_data = {"username": fake_username(), "password": fake_password()} + registration_data = { + "username": test_data["username"], + "password": test_data["password"], + } registration_result = soap_client.service.account_register(registration_data) assert registration_result.error is False - login_data = {"username": "pedro19", "password": "pedro19"} - login_response = app.test_client().post("/auth_login", json=login_data) + login_data = {"username": test_data["username"], "password": test_data["password"]} + login_response = app.test_client().post("/auth/login", json=login_data) + login_json_response = json.loads(login_response.data) assert login_response.status_code == 200 - jwt = json.loads(login_response.data)["jwt"] - data = {"jwt": jwt + "a"} - response = app.test_client().post("/auth_refresh", json=data) + jwt = login_json_response["token"] + not_valid_jwt = jwt + "wrong" + response = app.test_client().post( + "/auth/refresh", headers={"Authorization": "Bearer " + not_valid_jwt} + ) + response_json = json.loads(response.data) assert response.status_code == 401 - - response_data = json.loads(response.data) - assert "msg" in response_data - assert response_data["msg"] != "" + assert response_json["msg"] != "" def test_challenge_valid_token() -> None: - registration_data = {"username": "gloria1", "password": "gloria1"} + test_data = {"username": fake_username(), "password": fake_password()} + registration_data = { + "username": test_data["username"], + "password": test_data["password"], + } registration_result = soap_client.service.account_register(registration_data) assert registration_result.error is False - login_data = {"username": "gloria1", "password": "gloria1"} - login_response = app.test_client().post("/auth_login", json=login_data) + login_data = {"username": test_data["username"], "password": test_data["password"]} + login_response = app.test_client().post("/auth/login", json=login_data) + login_json_response = json.loads(login_response.data) assert login_response.status_code == 200 - jwt = json.loads(login_response.data)["jwt"] - data = {"jwt": jwt} - response = app.test_client().post("/auth_refresh", json=data) + jwt = login_json_response["token"] + response = app.test_client().post( + "/auth/refresh", headers={"Authorization": "Bearer " + jwt} + ) + response_json = json.loads(response.data) assert response.status_code == 200 - - # Verificar si la respuesta contiene un token JWT válido - response_data = json.loads(response.data) - assert "jwt" in response_data - assert response_data["jwt"] != "" + assert response_json["token"] != "" From 77e1bf2e86debfa3a5a619839dede4f7373087bb Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Mon, 9 Oct 2023 01:13:57 +0000 Subject: [PATCH 10/50] [ci skip] chore(release): 0.0.7 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85afce8..e6aad8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.7 (2023-10-09) + ### 0.0.6 (2023-10-08) diff --git a/package-lock.json b/package-lock.json index dfe5b9f..be58b08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.6", + "version": "0.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.6", + "version": "0.0.7", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 6697cfe..73d9be7 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.6" + "version": "0.0.7" } From d9b08051f215b5604fe37d55c1e6daed8361988a Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Mon, 9 Oct 2023 01:16:41 +0000 Subject: [PATCH 11/50] [ci skip] chore(release): 0.0.8 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6aad8a..f91fbbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.8 (2023-10-09) + ### 0.0.7 (2023-10-09) ### 0.0.6 (2023-10-08) diff --git a/package-lock.json b/package-lock.json index be58b08..bce946d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.7", + "version": "0.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.7", + "version": "0.0.8", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 73d9be7..b5444f4 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.7" + "version": "0.0.8" } From 3ba394f1af72fff64d6cbfbab2b5397f8ed39ea6 Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:43:50 -0500 Subject: [PATCH 12/50] fix: Enable CORS (#40) --- CLI.md | 10 ++++++++++ Dockerfile | 1 + main.py | 6 ++++++ requirements.txt | 1 + src/config/environment.py | 1 + 5 files changed, 19 insertions(+) create mode 100644 CLI.md diff --git a/CLI.md b/CLI.md new file mode 100644 index 0000000..04b6c61 --- /dev/null +++ b/CLI.md @@ -0,0 +1,10 @@ +# CLI + +This document describes how to use the service as a CLI tool. + +## Environment variables + +| Variable | Description | Example | +| ----------------- | ------------------------------------------------------ | ------------------------------------------ | +| `GATEWAY_BASEURL` | Base URL of the gateway | `https://gateway:8080` | +| `ALLOWED_ORIGINS` | An string with the allowed origins separated by commas | `https://example.com,https://example2.com` | diff --git a/Dockerfile b/Dockerfile index f78f500..bacbdbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,4 +6,5 @@ COPY . . USER application RUN pip install -r requirements.txt ENV GATEWAY_BASEURL "http://gateway:8080" +ENV ALLOWED_ORIGINS "http://localhost:5173" ENTRYPOINT ["python", "-m", "flask", "-A", "./main.py", "run", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/main.py b/main.py index c8edc98..b7e2b7e 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,14 @@ import flask +from flask_cors import CORS from src.views import views +from src.config.environment import variables app = flask.Flask(__name__) app.register_blueprint(views) +allowed_origins_list = variables["ALLOWED_ORIGINS"].split(",") + +CORS(app, resources={r"/*": {"origins": allowed_origins_list}}) + if __name__ == "__main__": app.run(debug=True) diff --git a/requirements.txt b/requirements.txt index 4661a8c..d1daf10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,3 +39,4 @@ urllib3==2.0.4 Werkzeug==2.3.7 wrapt==1.15.0 zeep==4.2.1 +flask-cors==4.0.0 \ No newline at end of file diff --git a/src/config/environment.py b/src/config/environment.py index 5f7962d..f2724f4 100644 --- a/src/config/environment.py +++ b/src/config/environment.py @@ -2,4 +2,5 @@ variables = { "GATEWAY_BASEURL": os.getenv("GATEWAY_BASEURL", "http://localhost:8080"), + "ALLOWED_ORIGINS": os.getenv("ALLOWED_ORIGINS", "http://localhost:5173"), } From 38392d476c718eacc7e70e0bed98de08f2f1d186 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:26:41 -0500 Subject: [PATCH 13/50] feat: Register endpoint (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: enpoint registration coverage 57% Implementation of the registration endpoint, 57% coverage is achieved * style: Fix formatter and linter warnings * test: Use random data in register tests * refactor: Update register endpoint according to the `.net` spec * test: Fix broken test --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> Co-authored-by: Pedro Andrés Chaparro Quintero --- docs/spec.openapi.yaml | 4 +-- src/controllers/_account_controllers.py | 31 ++++++++++++++++ src/views/__init__.py | 2 ++ src/views/_account_views.py | 9 +++++ src/views/_account_views_test.py | 48 +++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/controllers/_account_controllers.py create mode 100644 src/views/_account_views.py create mode 100644 src/views/_account_views_test.py diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 7f150bf..b777cbe 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -43,7 +43,7 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /register: + /account/register: post: tags: - Authentication @@ -577,7 +577,7 @@ components: authorization: type: object properties: - jwt: + token: type: string fileDetails: type: object diff --git a/src/controllers/_account_controllers.py b/src/controllers/_account_controllers.py new file mode 100644 index 0000000..fc82a38 --- /dev/null +++ b/src/controllers/_account_controllers.py @@ -0,0 +1,31 @@ +from flask import request +from src.config.soap_client import soap_client + + +def register_handler(): + try: + # Get JSON data from the request + data = request.json + + if not data: + return {"msg": "No JSON data provided in the request"}, 400 + + username = data.get("username") + password = data.get("password") + + if not username or not password: + return {"msg": "Required fields are missing in JSON data"}, 400 + + result = soap_client.service.account_register( + {"username": username, "password": password} + ) + + if result.auth is not None: + jwt = result.auth.token + return {"msg": "Register succeeded", "token": jwt}, 200 + + return {"msg": "Username already registered"}, 409 + + except Exception as e: + print("[Exception] register_handler ->", str(e)) + return {"msg": "Internal error", "error": str(e)}, 500 diff --git a/src/views/__init__.py b/src/views/__init__.py index 127bccd..4b1c006 100644 --- a/src/views/__init__.py +++ b/src/views/__init__.py @@ -1,5 +1,6 @@ import flask from ._authentication_views import views as authentication_views +from ._account_views import views as account_views # NOTE: Register all views / routes using the following blueprint @@ -7,3 +8,4 @@ views.register_blueprint(authentication_views) +views.register_blueprint(account_views) diff --git a/src/views/_account_views.py b/src/views/_account_views.py new file mode 100644 index 0000000..60f98e6 --- /dev/null +++ b/src/views/_account_views.py @@ -0,0 +1,9 @@ +import flask +from src.controllers import _account_controllers + +views = flask.Blueprint("account", __name__) + + +@views.route("/account/register", methods=["POST"]) +def account_register(): + return _account_controllers.register_handler() diff --git a/src/views/_account_views_test.py b/src/views/_account_views_test.py new file mode 100644 index 0000000..2d37c36 --- /dev/null +++ b/src/views/_account_views_test.py @@ -0,0 +1,48 @@ +import json +from main import app +from src.lib.faker import fake_username, fake_password + +register_test_data = {"username": fake_username(), "password": fake_password()} + + +def test_account_register_successful() -> None: + register_data = { + "username": register_test_data["username"], + "password": register_test_data["password"], + } + response = app.test_client().post("/account/register", json=register_data) + json_response = json.loads(response.data) + + assert response.status_code == 200 + assert json.loads(response.data)["msg"] == "Register succeeded" + assert json_response["token"] != "" + + +def test_account_register_missing_fields() -> None: + # Empty json + data = {} + response = app.test_client().post("/account/register", json=data) + + assert response.status_code == 400 + assert json.loads(response.data)["msg"] == "No JSON data provided in the request" + + # Missing password + data = {"username": register_test_data["username"]} + response = app.test_client().post("/account/register", json=data) + + assert response.status_code == 400 + assert ( + json.loads(response.data)["msg"] == "Required fields are missing in JSON data" + ) + + +def test_account_register_Username_already_registered() -> None: + data = { + "username": register_test_data["username"], + "password": register_test_data["password"], + } + response = app.test_client().post("/account/register", json=data) + json_response = json.loads(response.data) + + assert response.status_code == 409 + assert json_response["msg"] == "Username already registered" From d8d3871d61e028b041a659a1a4ebaf72bb83b6e5 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Mon, 9 Oct 2023 19:29:15 +0000 Subject: [PATCH 14/50] [ci skip] chore(release): 0.0.9 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91fbbf..a50b069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.9 (2023-10-09) + + +### Features + +* Register endpoint ([#39](https://github.com/hawks-atlanta/proxy-python/issues/39)) ([38392d4](https://github.com/hawks-atlanta/proxy-python/commit/38392d476c718eacc7e70e0bed98de08f2f1d186)) + ### 0.0.8 (2023-10-09) ### 0.0.7 (2023-10-09) diff --git a/package-lock.json b/package-lock.json index bce946d..4f7d9e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.8", + "version": "0.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.8", + "version": "0.0.9", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index b5444f4..65fd999 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.8" + "version": "0.0.9" } From 9f8ad6ae4dee09b18bd7e1f44d36f0c0ee7f4ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:07:14 -0500 Subject: [PATCH 15/50] fix: Enable all CORS origins (#44) * chore: Remove origins environment variable * fix: Enable all origins in CORS config --- CLI.md | 7 +++---- Dockerfile | 1 - main.py | 6 +----- src/config/environment.py | 1 - 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/CLI.md b/CLI.md index 04b6c61..3a078cf 100644 --- a/CLI.md +++ b/CLI.md @@ -4,7 +4,6 @@ This document describes how to use the service as a CLI tool. ## Environment variables -| Variable | Description | Example | -| ----------------- | ------------------------------------------------------ | ------------------------------------------ | -| `GATEWAY_BASEURL` | Base URL of the gateway | `https://gateway:8080` | -| `ALLOWED_ORIGINS` | An string with the allowed origins separated by commas | `https://example.com,https://example2.com` | +| Variable | Description | Example | +| ----------------- | ----------------------- | ---------------------- | +| `GATEWAY_BASEURL` | Base URL of the gateway | `https://gateway:8080` | diff --git a/Dockerfile b/Dockerfile index bacbdbd..f78f500 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,5 +6,4 @@ COPY . . USER application RUN pip install -r requirements.txt ENV GATEWAY_BASEURL "http://gateway:8080" -ENV ALLOWED_ORIGINS "http://localhost:5173" ENTRYPOINT ["python", "-m", "flask", "-A", "./main.py", "run", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/main.py b/main.py index b7e2b7e..19c6768 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,10 @@ import flask from flask_cors import CORS from src.views import views -from src.config.environment import variables app = flask.Flask(__name__) app.register_blueprint(views) - -allowed_origins_list = variables["ALLOWED_ORIGINS"].split(",") - -CORS(app, resources={r"/*": {"origins": allowed_origins_list}}) +CORS(app, resources={r"/*": {"origins": "*"}}) if __name__ == "__main__": app.run(debug=True) diff --git a/src/config/environment.py b/src/config/environment.py index f2724f4..5f7962d 100644 --- a/src/config/environment.py +++ b/src/config/environment.py @@ -2,5 +2,4 @@ variables = { "GATEWAY_BASEURL": os.getenv("GATEWAY_BASEURL", "http://localhost:8080"), - "ALLOWED_ORIGINS": os.getenv("ALLOWED_ORIGINS", "http://localhost:5173"), } From 5292585bc1fb0bb20c5cce8369f5203d91e8efc5 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Wed, 11 Oct 2023 00:10:06 +0000 Subject: [PATCH 16/50] [ci skip] chore(release): 0.0.10 --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a50b069..9622a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.10 (2023-10-11) + + +### Bug Fixes + +* Enable all CORS origins ([#44](https://github.com/hawks-atlanta/proxy-python/issues/44)) ([9f8ad6a](https://github.com/hawks-atlanta/proxy-python/commit/9f8ad6ae4dee09b18bd7e1f44d36f0c0ee7f4ced)) + ### 0.0.9 (2023-10-09) diff --git a/package-lock.json b/package-lock.json index 4f7d9e6..4dcd6c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.9", + "version": "0.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.9", + "version": "0.0.10", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 65fd999..c7a9e87 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.9" + "version": "0.0.10" } From b26882195373b5155c9a3c5cd52787f9071411b9 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Wed, 11 Oct 2023 00:11:55 +0000 Subject: [PATCH 17/50] [ci skip] chore(release): 0.0.11 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9622a2b..8706aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### 0.0.11 (2023-10-11) + ### 0.0.10 (2023-10-11) diff --git a/package-lock.json b/package-lock.json index 4dcd6c0..9149c7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "frontend-python", - "version": "0.0.10", + "version": "0.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { - "version": "0.0.10", + "version": "0.0.11", "devDependencies": { "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" diff --git a/package.json b/package.json index c7a9e87..90587f1 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,5 @@ "git-semver-tags": "^4.1.1", "standard-version": "^9.5.0" }, - "version": "0.0.10" + "version": "0.0.11" } From 7da7a7ccb4ed43e205e59775bd368a0ae2524b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:06:51 -0500 Subject: [PATCH 18/50] ci: Fix tagging pipeline (#48) --- .github/workflows/tagging.yaml | 22 +- CHANGELOG.md | 60 - package-lock.json | 2095 -------------------------------- package.json | 7 - version.json | 3 + version.py | 2 +- 6 files changed, 12 insertions(+), 2177 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 package-lock.json delete mode 100644 package.json create mode 100644 version.json diff --git a/.github/workflows/tagging.yaml b/.github/workflows/tagging.yaml index f985489..0acaca6 100644 --- a/.github/workflows/tagging.yaml +++ b/.github/workflows/tagging.yaml @@ -11,18 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Setup node 18.x - uses: actions/setup-node@v3 + + - uses: TriPSs/conventional-changelog-action@v3 + name: Changelog + id: changelog with: - node-version: 18.x - cache: "npm" - - name: Git Identity - run: | - git checkout dev - git fetch --all --tags - git config --global user.email "antoniojosedonishung@gmail.com" - git config --global user.name "Antonio Donis" - - name: Changelog - run: 'npx standard-version --message "[ci skip] chore(release): %s"' - - name: Push changes - run: git push --follow-tags --force origin dev \ No newline at end of file + git-user-nane: "Antonio Donis" + git-user-email: "antoniojosedonishung@gmail.com" + git-message: "[ci skip] chore(release): {version}" + version-file: "version.json" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 8706aa5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,60 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. - -### 0.0.11 (2023-10-11) - -### 0.0.10 (2023-10-11) - - -### Bug Fixes - -* Enable all CORS origins ([#44](https://github.com/hawks-atlanta/proxy-python/issues/44)) ([9f8ad6a](https://github.com/hawks-atlanta/proxy-python/commit/9f8ad6ae4dee09b18bd7e1f44d36f0c0ee7f4ced)) - -### 0.0.9 (2023-10-09) - - -### Features - -* Register endpoint ([#39](https://github.com/hawks-atlanta/proxy-python/issues/39)) ([38392d4](https://github.com/hawks-atlanta/proxy-python/commit/38392d476c718eacc7e70e0bed98de08f2f1d186)) - -### 0.0.8 (2023-10-09) - -### 0.0.7 (2023-10-09) - -### 0.0.6 (2023-10-08) - - -### Features - -* endpoint challenge ([#33](https://github.com/hawks-atlanta/proxy-python/issues/33)) ([ee78fb6](https://github.com/hawks-atlanta/proxy-python/commit/ee78fb6864d0f6616464439c5c177884e514cf29)) - -### 0.0.5 (2023-10-04) - - -### Features - -* endpoint login ([#15](https://github.com/hawks-atlanta/proxy-python/issues/15)) ([4921ca1](https://github.com/hawks-atlanta/proxy-python/commit/4921ca11b886d950d7811f67ac573b7a9adc6831)) - -### 0.0.4 (2023-08-24) - -### 0.0.3 (2023-08-24) - - -### Bug Fixes - -* coverage pipeline correct xml format ([#7](https://github.com/hawks-atlanta/proxy-python/issues/7)) ([1002efa](https://github.com/hawks-atlanta/proxy-python/commit/1002efa3c86bcdc2e7954f8f9394780ac5f41198)) - -### 0.0.2 (2023-08-23) - -### 0.0.1 (2023-08-23) - - -### Features - -* docker-compose ([6e44d01](https://github.com/hawks-atlanta/frontend-python/commit/6e44d01abede6ace6b488c6131bc96bd3b884c6d)) - - -### Bug Fixes - -* base flask repository ([3f8ea73](https://github.com/hawks-atlanta/frontend-python/commit/3f8ea739fc267bc3bf6dc15c4cf540c511fe9419)) diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 9149c7d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2095 +0,0 @@ -{ - "name": "frontend-python", - "version": "0.0.11", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "version": "0.0.11", - "devDependencies": { - "git-semver-tags": "^4.1.1", - "standard-version": "^9.5.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "dev": true, - "engines": [ - "node >= 6.0" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/conventional-changelog": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", - "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", - "dev": true, - "dependencies": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-config-spec": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", - "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", - "dev": true - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", - "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", - "dev": true, - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/conventional-changelog-core/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", - "dev": true, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/conventional-changelog-core/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", - "dev": true, - "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "dev": true, - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", - "dev": true, - "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", - "dev": true, - "dependencies": { - "concat-stream": "^2.0.0", - "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-filter": "^2.0.7", - "conventional-commits-parser": "^3.2.0", - "git-raw-commits": "^2.0.8", - "git-semver-tags": "^4.1.1", - "meow": "^8.0.0", - "q": "^1.5.1" - }, - "bin": { - "conventional-recommended-bump": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotgitignore": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", - "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", - "dev": true, - "dependencies": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "get-pkg-repo": "src/cli.js" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-pkg-repo/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/get-pkg-repo/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", - "dev": true, - "dependencies": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", - "dev": true, - "dependencies": { - "meow": "^8.0.0", - "semver": "^6.0.0" - }, - "bin": { - "git-semver-tags": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", - "dev": true, - "dependencies": { - "ini": "^1.3.2" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", - "dev": true, - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", - "dev": true - }, - "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/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/standard-version": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", - "integrity": "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "conventional-changelog": "3.1.25", - "conventional-changelog-config-spec": "2.1.0", - "conventional-changelog-conventionalcommits": "4.6.3", - "conventional-recommended-bump": "6.1.0", - "detect-indent": "^6.0.0", - "detect-newline": "^3.1.0", - "dotgitignore": "^2.1.0", - "figures": "^3.1.0", - "find-up": "^5.0.0", - "git-semver-tags": "^4.0.0", - "semver": "^7.1.1", - "stringify-package": "^1.0.1", - "yargs": "^16.0.0" - }, - "bin": { - "standard-version": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-version/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "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/stringify-package": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", - "deprecated": "This module is not used anymore, and has been replaced by @npmcli/package-json", - "dev": true - }, - "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-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "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/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 90587f1..0000000 --- a/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "devDependencies": { - "git-semver-tags": "^4.1.1", - "standard-version": "^9.5.0" - }, - "version": "0.0.11" -} diff --git a/version.json b/version.json new file mode 100644 index 0000000..c158d5b --- /dev/null +++ b/version.json @@ -0,0 +1,3 @@ +{ + "version": "0.0.0" +} \ No newline at end of file diff --git a/version.py b/version.py index 6ee98f8..08b00ca 100644 --- a/version.py +++ b/version.py @@ -1,5 +1,5 @@ import json -with open("package.json", "rb") as pkg_file: +with open("version.json", "rb") as pkg_file: pkg = json.load(pkg_file) print(f"version={pkg['version']}") From a32f4427afb8ebd3fcb180bf398fbb7ea871f311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= Date: Wed, 11 Oct 2023 20:10:59 -0500 Subject: [PATCH 19/50] ci: Fix typo --- .github/workflows/tagging.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tagging.yaml b/.github/workflows/tagging.yaml index 0acaca6..4bbcbd0 100644 --- a/.github/workflows/tagging.yaml +++ b/.github/workflows/tagging.yaml @@ -16,7 +16,8 @@ jobs: name: Changelog id: changelog with: - git-user-nane: "Antonio Donis" + git-user-name: "Antonio Donis" git-user-email: "antoniojosedonishung@gmail.com" git-message: "[ci skip] chore(release): {version}" + git-branch: "dev" version-file: "version.json" \ No newline at end of file From 9dc695992b954c02ef84600b2df4d8d89e39d489 Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Wed, 11 Oct 2023 20:39:23 -0500 Subject: [PATCH 20/50] feat: update docs openapi (#41) --- docs/spec.openapi.yaml | 435 ++++++++++++++++++++++++----------------- 1 file changed, 257 insertions(+), 178 deletions(-) diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index b777cbe..71c984e 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -10,7 +10,6 @@ info: tags: - name: Authentication - name: Account - - name: Authorization - name: Files paths: @@ -25,8 +24,8 @@ paths: schema: $ref: "#/components/schemas/credentials" responses: - "200": - description: Login succeed + "201": + description: Login succeeded content: application/json: schema: @@ -43,64 +42,36 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /account/register: - post: - tags: - - Authentication - description: Register the new user in the service - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/credentials" - responses: - "201": - description: Registration succeed - content: - application/json: - schema: - $ref: "#/components/schemas/authorization" - "409": - description: Username already registered - content: - application/json: - schema: - $ref: "#/components/schemas/statusResponse" - "500": - description: Internal error - content: - application/json: - schema: - $ref: "#/components/schemas/statusResponse" + /auth/refresh: post: tags: - - Authorization - security: - - bearer: [] + - Authentication + security: + - bearer: [] description: Verifies the received token is still valid responses: "200": - description: The current token is valid and a new token was obtained successfully. + description: The current token is valid, and a new token was obtained successfully. content: application/json: schema: type: object properties: - msg: + msg: type: string - example: "Login successfull" - token: + example: "Token refreshed successfully" + token: type: string - example: "{jwt}" + example: "{new_jwt}" "400": - description: JSON body wasn't given or was not valid. + description: JSON body wasn't given or was not valid. content: application/json: schema: $ref: "#/components/schemas/statusResponse" "401": - description: No authorization header was given. + description: No authorization header was given or the token is invalid. content: application/json: schema: @@ -111,32 +82,26 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /account/password: - patch: + + /account/register: + post: tags: - Account - description: Updates the username password + description: Register a new user in the service requestBody: content: application/json: schema: - allOf: - - $ref: "#/components/schemas/authorization" - - type: object - properties: - currentPassword: - type: string - newPassword: - type: string + $ref: "#/components/schemas/credentials" responses: - "200": - description: Password updated successfully + "201": + description: Registration successful content: application/json: schema: - $ref: "#/components/schemas/statusResponse" - "401": - description: Unauthorized + $ref: "#/components/schemas/authorization" + "409": + description: Username is already registered content: application/json: schema: @@ -148,71 +113,79 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files: + /file/upload: post: tags: - Files - description: Create a new file + security: + - bearer: [] + description: Upload a file requestBody: content: multipart/form-data: schema: type: object properties: + file: + type: string + format: binary fileName: type: string + example: "picture" location: type: string - file: - type: string + example: "5295d524-aafc-407c-96ed-adae2cd5047a" responses: "201": - description: File created successfully - content: - application/json: - schema: - $ref: "#/components/schemas/statusResponse" - "401": - description: Not authorized + description: File upload successful content: application/json: schema: - $ref: "#/components/schemas/statusResponse" + type: object + properties: + fileUUID: + type: string "500": - description: Internal error + description: Internal Server Error content: application/json: schema: $ref: "#/components/schemas/statusResponse" - /folders: - post: + /file/download/{fileUUID}: + get: tags: - Files - description: Create a new folder - requestBody: - content: - application/json: - schema: - type: object - properties: - folderName: - type: string - location: - type: string #UUID + security: + - bearer: [] + description: Download a file by UUID + parameters: + - in: path + name: fileUUID + required: true + schema: + type: string + description: The UUID of the file to download responses: - "201": - description: Folder created successfully + "200": + description: File downloaded successfully content: - application/json: + application/file: schema: - $ref: "#/components/schemas/statusResponse" + type: string + format: binary "401": description: Not authorized content: application/json: schema: $ref: "#/components/schemas/statusResponse" + "404": + description: File not found + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" "500": description: Internal error content: @@ -220,20 +193,32 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/shared: + /file/check/{fileUUID}: get: tags: - Files - description: Gets the list of shared files + security: + - bearer: [] + description: Check if the given file is ready + parameters: + - in: path + name: fileUUID + required: true + schema: + type: string responses: "200": - description: List of shared files obtained + description: The state of the file was retreived successfully content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/fileDetailsShared" + allOf: + - $ref: "#/components/schemas/statusResponse" + type: object + properties: + ready: + type: boolean + example: true "401": description: Not authorized content: @@ -246,25 +231,33 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /files/{fileId}/edit: - put: + + /file/{fileUUID}/move: + patch: tags: - Files - description: Edit an existing file + security: + - bearer: [] + description: Move an existing file to a different location parameters: - in: path - name: fileId - required: true - schema: - type: string - - in: query - name: newContent + name: fileUUID required: true schema: type: string + description: The ID of the file to move + requestBody: + content: + application/json: + schema: + type: object + properties: + targetDirectoryUUID: + type: string + example: "fbef8fdb-d16e-436b-8835-b96091b0b9ce" responses: "200": - description: File successfully edited + description: File moved successfully content: application/json: schema: @@ -281,20 +274,33 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /files/{fileId}: - delete: + + /file/{fileUUID}/rename: + patch: tags: - Files - description: Deletes an existing file + security: + - bearer: [] + description: Move an existing file to a different location parameters: - in: path - name: fileId + name: fileUUID required: true schema: type: string + description: The ID of the file to move + requestBody: + content: + application/json: + schema: + type: object + properties: + newName: + type: string + example: "awesome_name" responses: "200": - description: File successfully deleted + description: File moved successfully content: application/json: schema: @@ -311,25 +317,24 @@ paths: application/json: schema: $ref: "#/components/schemas/statusResponse" - /files/{fileId}/move: - patch: + + /file/{fileUUID}: + delete: tags: - Files - description: Move an existing file to a different location + security: + - bearer: [] + description: Deletes an existing file by fileUUID parameters: - in: path - name: fileId - required: true - schema: - type: string - - in: query - name: newLocation + name: fileUUID required: true schema: type: string + description: The ID of the file to delete responses: "200": - description: File moved successfully + description: File successfully deleted content: application/json: schema: @@ -347,10 +352,12 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/list: + /file/list: get: tags: - Files + security: + - bearer: [] description: List files in a given location parameters: - in: query @@ -381,38 +388,28 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/download/{fileUUID}: + /file/shared: get: tags: - Files - description: Download a file by UUID - parameters: - - in: path - name: fileUUID - required: true - schema: - type: string - description: The UUID of the file to download + security: + - bearer: [] + description: Gets the list of shared files responses: "200": - description: File downloaded successfully + description: List of shared files obtained content: - application/octet-stream: + application/json: schema: - type: string - format: binary + type: array + items: + $ref: "#/components/schemas/fileDetailsShared" "401": description: Not authorized content: application/json: schema: $ref: "#/components/schemas/statusResponse" - "404": - description: File not found - content: - application/json: - schema: - $ref: "#/components/schemas/statusResponse" "500": description: Internal error content: @@ -420,10 +417,12 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/share: + /file/share: post: tags: - Files + security: + - bearer: [] description: Share a file with a user requestBody: content: @@ -462,10 +461,57 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/unshare: + /file/shared-with-who: + get: + tags: + - Files + security: + - bearer: [] + description: Get the list of users the file is shared with + parameters: + - in: query + name: fileUUID + required: true + schema: + type: string + responses: + "200": + description: List of users the file is shared with + content: + application/json: + schema: + type: object + properties: + users: + type: array + items: + type: string + example: sulcud + "401": + description: Not authorized + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" + "404": + description: File not found + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" + "500": + description: Internal error + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" + + /file/unshare: post: tags: - Files + security: + - bearer: [] description: Unshare a file with a user requestBody: content: @@ -504,39 +550,69 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /files/sharedwithwho: - get: + /account/password: + patch: tags: - - Files - description: Get the list of users the file is shared with - parameters: - - in: query - name: fileUUID - required: true - schema: - type: string + - Account + security: + - bearer: [] + description: Updates the password + requestBody: + content: + application/json: + schema: + type: object + properties: + oldPassword: + type: string + newPassword: + type: string responses: "200": - description: List of users the file is shared with + description: Password updated successfully content: application/json: schema: - type: object - properties: - users: - type: array - items: - type: string - example: sulcud - + $ref: "#/components/schemas/statusResponse" "401": - description: Not authorized + description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/statusResponse" - "404": - description: File not found + "500": + description: Internal error + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" + + /folders: + post: + tags: + - Files + security: + - bearer: [] + description: Create a new folder + requestBody: + content: + application/json: + schema: + type: object + properties: + folderName: + type: string + location: + type: string #UUID + responses: + "201": + description: Folder created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/statusResponse" + "401": + description: Not authorized content: application/json: schema: @@ -550,16 +626,11 @@ paths: components: securitySchemes: - bearer: - type: http - scheme: bearer - bearerFormat: JWT + bearer: + type: http + scheme: bearer + bearerFormat: JWT schemas: - account: - type: object - properties: - uuid: - type: string statusResponse: type: object properties: @@ -579,30 +650,38 @@ components: properties: token: type: string + example: "{jwt}" fileDetails: type: object properties: - fileName: + name: type: string - fileSize: + example: "pixture.png" + size: type: integer + example: 4687 isFile: type: boolean - fileUUID: + uuid: type: string + example: "5295d524-aafc-407c-96ed-adae2cd5047a" fileDetailsShared: type: object properties: - fileName: + name: type: string - fileSize: + example: "pixture.png" + size: type: integer + example: 4687 isFile: type: boolean - fileUUID: + uuid: type: string + example: "5295d524-aafc-407c-96ed-adae2cd5047a" ownerusername: type: string + example: "CocoTheCat" shareWithReq: type: object properties: From ba8ff155fe79d9ccc39772f792afc7941460bb8c Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Thu, 12 Oct 2023 01:39:36 +0000 Subject: [PATCH 21/50] [ci skip] chore(release): v0.1.0 [skip ci] --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ version.json | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..762b002 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,40 @@ +# [0.1.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.11...v0.1.0) (2023-10-12) + + +### Features + +* update docs openapi ([#41](https://github.com/hawks-atlanta/proxy-python/issues/41)) ([9dc6959](https://github.com/hawks-atlanta/proxy-python/commit/9dc695992b954c02ef84600b2df4d8d89e39d489)) + + + +## [0.0.11](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.10...v0.0.11) (2023-10-11) + + + +## [0.0.10](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.9...v0.0.10) (2023-10-11) + + +### Bug Fixes + +* Enable all CORS origins ([#44](https://github.com/hawks-atlanta/proxy-python/issues/44)) ([9f8ad6a](https://github.com/hawks-atlanta/proxy-python/commit/9f8ad6ae4dee09b18bd7e1f44d36f0c0ee7f4ced)) + + + +## [0.0.9](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.8...v0.0.9) (2023-10-09) + + +### Bug Fixes + +* Enable CORS ([#40](https://github.com/hawks-atlanta/proxy-python/issues/40)) ([3ba394f](https://github.com/hawks-atlanta/proxy-python/commit/3ba394f1af72fff64d6cbfbab2b5397f8ed39ea6)) + + +### Features + +* Register endpoint ([#39](https://github.com/hawks-atlanta/proxy-python/issues/39)) ([38392d4](https://github.com/hawks-atlanta/proxy-python/commit/38392d476c718eacc7e70e0bed98de08f2f1d186)) + + + +## [0.0.8](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.7...v0.0.8) (2023-10-09) + + + diff --git a/version.json b/version.json index c158d5b..6793ca7 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.0.0" + "version": "0.1.0" } \ No newline at end of file From af9507083f7b2b5a833cb0ac50c865806778ca5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Thu, 12 Oct 2023 06:07:21 -0500 Subject: [PATCH 22/50] feat: Endpoint to get the file status (#49) * refactor: Remove exception from responses * ci: Remove unnecessary field in tagging pipeline * fix: Update metadata base url in docker-compose * feat(file): Endpoint to get the state of a file * test(file): Add tests to the endpoint to get file state * chore: Set current version --- .github/workflows/tagging.yaml | 1 - docker-compose.yaml | 2 +- src/controllers/_account_controllers.py | 2 +- .../_authentication_controllers.py | 4 +- src/controllers/_file_controllers.py | 20 +++++ src/views/__init__.py | 2 + src/views/_file_views.py | 12 +++ src/views/_file_views_test.py | 87 +++++++++++++++++++ version.json | 2 +- 9 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 src/controllers/_file_controllers.py create mode 100644 src/views/_file_views.py create mode 100644 src/views/_file_views_test.py diff --git a/.github/workflows/tagging.yaml b/.github/workflows/tagging.yaml index 4bbcbd0..24e7c7d 100644 --- a/.github/workflows/tagging.yaml +++ b/.github/workflows/tagging.yaml @@ -19,5 +19,4 @@ jobs: git-user-name: "Antonio Donis" git-user-email: "antoniojosedonishung@gmail.com" git-message: "[ci skip] chore(release): {version}" - git-branch: "dev" version-file: "version.json" \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index d48e5d2..38108fa 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,7 +9,7 @@ services: ports: - "127.0.0.1:8080:8080" environment: - METADATA_BASEURL: http://metadata:8080 + METADATA_BASEURL: http://metadata:8080/api/v1 AUTHENTICATION_BASEURL: http://authentication:8080 WORKER_HOST: worker WORKER_PORT: 1099 diff --git a/src/controllers/_account_controllers.py b/src/controllers/_account_controllers.py index fc82a38..b63fa68 100644 --- a/src/controllers/_account_controllers.py +++ b/src/controllers/_account_controllers.py @@ -28,4 +28,4 @@ def register_handler(): except Exception as e: print("[Exception] register_handler ->", str(e)) - return {"msg": "Internal error", "error": str(e)}, 500 + return {"msg": "There was an error registering the user"}, 500 diff --git a/src/controllers/_authentication_controllers.py b/src/controllers/_authentication_controllers.py index d0cb32a..3a612cc 100644 --- a/src/controllers/_authentication_controllers.py +++ b/src/controllers/_authentication_controllers.py @@ -22,7 +22,7 @@ def login_handler(): except Exception as e: print("[Exception] login_handler ->", str(e)) - return {"msg": "Internal error", "error": str(e)}, 500 + return {"msg": "There was an error logging in"}, 500 def challenge_handler(token): @@ -39,4 +39,4 @@ def challenge_handler(token): except Exception as e: print("[Exception] challenge ->", str(e)) - return {"msg": "Internal error: " + str(e)}, 500 + return {"msg": "There was an error validating or refreshing the session"}, 500 diff --git a/src/controllers/_file_controllers.py b/src/controllers/_file_controllers.py new file mode 100644 index 0000000..49696f6 --- /dev/null +++ b/src/controllers/_file_controllers.py @@ -0,0 +1,20 @@ +from src.config.soap_client import soap_client + + +def check_state_handler(token, file_uuid): + try: + response = soap_client.service.file_check( + {"token": token, "fileUUID": file_uuid} + ) + + has_success_code = str(response.code).startswith("20") + if not has_success_code: + return {"msg": response.msg}, response.code + + return { + "msg": "File status has been obtained successfully", + "ready": response.ready, + }, response.code + except Exception as e: + print("[Exception] check_state_handler ->", str(e)) + return {"msg": "There was an error checking the file state"}, 500 diff --git a/src/views/__init__.py b/src/views/__init__.py index 4b1c006..cc68bc6 100644 --- a/src/views/__init__.py +++ b/src/views/__init__.py @@ -1,6 +1,7 @@ import flask from ._authentication_views import views as authentication_views from ._account_views import views as account_views +from ._file_views import views as file_views # NOTE: Register all views / routes using the following blueprint @@ -9,3 +10,4 @@ views.register_blueprint(authentication_views) views.register_blueprint(account_views) +views.register_blueprint(file_views) diff --git a/src/views/_file_views.py b/src/views/_file_views.py new file mode 100644 index 0000000..d95e12f --- /dev/null +++ b/src/views/_file_views.py @@ -0,0 +1,12 @@ +import flask +from src.middlewares import auth_middlewares +from src.controllers import _file_controllers + + +views = flask.Blueprint("file", __name__) + + +@views.route("/file//status", methods=["GET"]) +@auth_middlewares.token_required +def file_check(token, file_uuid): + return _file_controllers.check_state_handler(token, file_uuid) diff --git a/src/views/_file_views_test.py b/src/views/_file_views_test.py new file mode 100644 index 0000000..5fb1875 --- /dev/null +++ b/src/views/_file_views_test.py @@ -0,0 +1,87 @@ +import json +from uuid import uuid4 +from random import randbytes +from main import app +from config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +get_status_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "name": "test_file.txt", + "content": randbytes(1024), + }, +} + + +def test_get_status_not_success_code(): + # Register an user + register_response = soap_client.service.account_register( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Try to get status of the file + random_file_uuid = uuid4() + response = soap_client.service.file_check( + {"token": token, "fileUUID": random_file_uuid} + ) + + assert response.code == 404 + assert response.msg == f"There is no file with the {random_file_uuid} UUID" + + +def test_get_status_success_code(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file + upload_response = soap_client.service.file_upload( + { + "token": token, + "fileName": get_status_test_data["file"]["name"], + "fileContent": get_status_test_data["file"]["content"], + "location": None, + } + ) + assert upload_response.error is False + + # Get status of the file + response = app.test_client().get( + f"/file/{upload_response.fileUUID}/status", + headers={"Authorization": f"Bearer {token}"}, + ) + + has_expected_success_code = ( + response.status_code == 200 or response.status_code == 202 + ) + assert has_expected_success_code + + json_response = json.loads(response.data) + has_boolean_ready = ( + json_response["ready"] is True or json_response["ready"] is False + ) + assert has_boolean_ready + + assert json_response["msg"] == "File status has been obtained successfully" diff --git a/version.json b/version.json index 6793ca7..085e3ba 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.1.0" + "version": "0.3.0" } \ No newline at end of file From 7345e4585ccf327c65364d468a3b3e0662290947 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Thu, 12 Oct 2023 11:07:40 +0000 Subject: [PATCH 23/50] [ci skip] chore(release): v0.4.0 [skip ci] --- CHANGELOG.md | 13 +++++++++---- version.json | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 762b002..0d0c0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.4.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.1.0...v0.4.0) (2023-10-12) + + +### Features + +* Endpoint to get the file status ([#49](https://github.com/hawks-atlanta/proxy-python/issues/49)) ([af95070](https://github.com/hawks-atlanta/proxy-python/commit/af9507083f7b2b5a833cb0ac50c865806778ca5d)) + + + # [0.1.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.11...v0.1.0) (2023-10-12) @@ -34,7 +43,3 @@ -## [0.0.8](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.7...v0.0.8) (2023-10-09) - - - diff --git a/version.json b/version.json index 085e3ba..39a8c86 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.3.0" + "version": "0.4.0" } \ No newline at end of file From b1e93fc468187e15341161c3db1084559b64e93c Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:44:21 -0500 Subject: [PATCH 24/50] feat: Endpoint to rename a file (#50) --- docs/spec.openapi.yaml | 8 +- src/controllers/_file_controllers.py | 24 ++++++ src/views/_file_views.py | 6 ++ src/views/_file_views_test.py | 109 +++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 4 deletions(-) diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 71c984e..388f036 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -281,14 +281,14 @@ paths: - Files security: - bearer: [] - description: Move an existing file to a different location + description: Rename an existing file parameters: - in: path name: fileUUID required: true schema: type: string - description: The ID of the file to move + description: The ID of the file to rename requestBody: content: application/json: @@ -297,10 +297,10 @@ paths: properties: newName: type: string - example: "awesome_name" + example: "new_file_name" responses: "200": - description: File moved successfully + description: File renamed successfully content: application/json: schema: diff --git a/src/controllers/_file_controllers.py b/src/controllers/_file_controllers.py index 49696f6..5aad7f0 100644 --- a/src/controllers/_file_controllers.py +++ b/src/controllers/_file_controllers.py @@ -1,3 +1,5 @@ +import json +from flask import request from src.config.soap_client import soap_client @@ -18,3 +20,25 @@ def check_state_handler(token, file_uuid): except Exception as e: print("[Exception] check_state_handler ->", str(e)) return {"msg": "There was an error checking the file state"}, 500 + + +def rename_handler(token, file_uuid): + try: + data = json.loads(request.data) + new_name = data["newName"] + if not new_name or len(new_name) == 0: + return {"msg": "New name is required"}, 400 + + request_data = {"token": token, "fileUUID": file_uuid, "newName": new_name} + response = soap_client.service.file_rename(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return {"msg": "The file has been renamed successfully"}, 200 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception: + return {"msg": "There was an error renaming the file"}, 500 diff --git a/src/views/_file_views.py b/src/views/_file_views.py index d95e12f..d99aa75 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -10,3 +10,9 @@ @auth_middlewares.token_required def file_check(token, file_uuid): return _file_controllers.check_state_handler(token, file_uuid) + + +@views.route("/file//rename", methods=["PATCH"]) +@auth_middlewares.token_required +def file_rename(token, file_uuid): + return _file_controllers.rename_handler(token, file_uuid) diff --git a/src/views/_file_views_test.py b/src/views/_file_views_test.py index 5fb1875..25754e5 100644 --- a/src/views/_file_views_test.py +++ b/src/views/_file_views_test.py @@ -5,6 +5,8 @@ from config.soap_client import soap_client from src.lib.faker import fake_username, fake_password + +# GET STATUS TESTS get_status_test_data = { "username": fake_username(), "password": fake_password(), @@ -85,3 +87,110 @@ def test_get_status_success_code(): assert has_boolean_ready assert json_response["msg"] == "File status has been obtained successfully" + + +# RENAME FILE TESTS +rename_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "uuid": None, + "name": "test-file", + "content": randbytes(1024), + }, +} + + +def test_rename_bad_request(): + # Register an user + register_response = soap_client.service.account_register( + { + "username": rename_test_data["username"], + "password": rename_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": rename_test_data["username"], + "password": rename_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # No JSON + response = app.test_client().patch( + f"/file/{rename_test_data['file']['uuid']}/rename", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + # Empty file name + response = app.test_client().patch( + f"/file/{rename_test_data['file']['uuid']}/rename", + json={"newName": ""}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_rename_success(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": rename_test_data["username"], + "password": rename_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file + create_file_response = soap_client.service.file_upload( + { + "token": token, + "fileName": rename_test_data["file"]["name"], + "fileContent": rename_test_data["file"]["content"], + "location": None, + } + ) + assert create_file_response.error is False + + # Rename the file + new_name = "renamed-test-file" + response = app.test_client().patch( + f"/file/{create_file_response.fileUUID}/rename", + json={"newName": new_name}, + headers={"Authorization": f"Bearer {token}"}, + ) + + json_response = json.loads(response.data) + assert response.status_code == 200 + assert json_response["msg"] == "The file has been renamed successfully" + + # Update the test data + rename_test_data["file"]["uuid"] = create_file_response.fileUUID + rename_test_data["file"]["name"] = new_name + + +def test_rename_conflict(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": rename_test_data["username"], + "password": rename_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Try to rename the file with the same name + response = app.test_client().patch( + f"/file/{rename_test_data['file']['uuid']}/rename", + json={"newName": rename_test_data["file"]["name"]}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 409 From 724cc9e84d049eab47c4a4d75e5ac2ab30de8181 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Fri, 13 Oct 2023 22:44:35 +0000 Subject: [PATCH 25/50] [ci skip] chore(release): v0.5.0 [skip ci] --- CHANGELOG.md | 23 +++++++++-------------- version.json | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0c0f0..fbd319b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.5.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.4.0...v0.5.0) (2023-10-13) + + +### Features + +* Endpoint to rename a file ([#50](https://github.com/hawks-atlanta/proxy-python/issues/50)) ([b1e93fc](https://github.com/hawks-atlanta/proxy-python/commit/b1e93fc468187e15341161c3db1084559b64e93c)) + + + # [0.4.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.1.0...v0.4.0) (2023-10-12) @@ -29,17 +38,3 @@ -## [0.0.9](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.8...v0.0.9) (2023-10-09) - - -### Bug Fixes - -* Enable CORS ([#40](https://github.com/hawks-atlanta/proxy-python/issues/40)) ([3ba394f](https://github.com/hawks-atlanta/proxy-python/commit/3ba394f1af72fff64d6cbfbab2b5397f8ed39ea6)) - - -### Features - -* Register endpoint ([#39](https://github.com/hawks-atlanta/proxy-python/issues/39)) ([38392d4](https://github.com/hawks-atlanta/proxy-python/commit/38392d476c718eacc7e70e0bed98de08f2f1d186)) - - - diff --git a/version.json b/version.json index 39a8c86..381a76d 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.4.0" + "version": "0.5.0" } \ No newline at end of file From 395b44bf9223cf1126521f80ac0ae1796d2207de Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:37:13 -0500 Subject: [PATCH 26/50] feat: Update password (#51) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Endpoint to update password + tests Endpoint to update password, with 42% coverage. Problems when querying with an invalid token * fix(account): Update endpoint to change the password * test(account): Update tests related to update password Increment coverage to 83% --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> Co-authored-by: Pedro Andrés Chaparro Quintero --- src/controllers/_account_controllers.py | 27 +++++++ src/views/_account_views.py | 7 ++ src/views/_account_views_test.py | 96 +++++++++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/src/controllers/_account_controllers.py b/src/controllers/_account_controllers.py index b63fa68..5070f3e 100644 --- a/src/controllers/_account_controllers.py +++ b/src/controllers/_account_controllers.py @@ -1,3 +1,4 @@ +import json from flask import request from src.config.soap_client import soap_client @@ -29,3 +30,29 @@ def register_handler(): except Exception as e: print("[Exception] register_handler ->", str(e)) return {"msg": "There was an error registering the user"}, 500 + + +def update_password_handler(token): + try: + data = json.loads(request.data) + oldPassword = data.get("oldPassword") + newPassword = data.get("newPassword") + + if not oldPassword or not newPassword or not token: + return {"msg": "Required fields are missing in JSON data"}, 400 + + result = soap_client.service.account_password( + {"oldpassword": oldPassword, "newpassword": newPassword, "token": token} + ) + + if result["error"] is True: + return {"msg": result["msg"]}, result["code"] + else: + return {"msg": "Password updated successfully"}, 200 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception as e: + print("[Exception] password_handler ->", str(e)) + return {"msg": "Internal error", "error": str(e)}, 500 diff --git a/src/views/_account_views.py b/src/views/_account_views.py index 60f98e6..20a72c8 100644 --- a/src/views/_account_views.py +++ b/src/views/_account_views.py @@ -1,4 +1,5 @@ import flask +from src.middlewares import auth_middlewares from src.controllers import _account_controllers views = flask.Blueprint("account", __name__) @@ -7,3 +8,9 @@ @views.route("/account/register", methods=["POST"]) def account_register(): return _account_controllers.register_handler() + + +@views.route("/account/password", methods=["PATCH"]) +@auth_middlewares.token_required +def account_password(token): + return _account_controllers.update_password_handler(token) diff --git a/src/views/_account_views_test.py b/src/views/_account_views_test.py index 2d37c36..4304ca6 100644 --- a/src/views/_account_views_test.py +++ b/src/views/_account_views_test.py @@ -1,7 +1,10 @@ import json from main import app +from config.soap_client import soap_client from src.lib.faker import fake_username, fake_password + +# REGISTER TEST'S register_test_data = {"username": fake_username(), "password": fake_password()} @@ -46,3 +49,96 @@ def test_account_register_Username_already_registered() -> None: assert response.status_code == 409 assert json_response["msg"] == "Username already registered" + + +# PASSWORD UPDATE TEST'S +update_password_test_data = {"username": fake_username(), "password": fake_password()} + + +def test_update_password_success() -> None: + # Register test user + register_response = soap_client.service.account_register( + { + "username": update_password_test_data["username"], + "password": update_password_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": update_password_test_data["username"], + "password": update_password_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Update the password + change_data = { + "oldPassword": update_password_test_data["password"], + "newPassword": "Andrea2", + } + response = app.test_client().patch( + "/account/password", + json=change_data, + headers={ + "Authorization": f"Bearer {token}", + }, + ) + + assert response.status_code == 200 + assert json.loads(response.data)["msg"] == "Password updated successfully" + update_password_test_data["password"] = change_data["newPassword"] + + +def test_update_password_missing_fields() -> None: + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": update_password_test_data["username"], + "password": update_password_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # No JSON + response = app.test_client().patch( + "/account/password", headers={"Authorization": f"Bearer {token}"} + ) + + assert response.status_code == 400 + assert ( + json.loads(response.data)["msg"] == "Invalid JSON data provided in the request" + ) + + # Missing fields + data = {} + response = app.test_client().patch( + "/account/password", + json=data, + headers={ + "Authorization": f"Bearer {token}", + }, + ) + + assert response.status_code == 400 + assert ( + json.loads(response.data)["msg"] == "Required fields are missing in JSON data" + ) + + +def test_update_password_not_valid_token() -> None: + change_data = {"oldPassword": "Andrea1", "newPassword": "Andrea2"} + response = app.test_client().patch( + "/account/password", + json=change_data, + headers={ + "Authorization": "Bearer 123", + }, + ) + + assert response.status_code == 401 + assert json.loads(response.data)["msg"] == "unauthorized" From 38a395eb338fe8c7ef54d961c3bc61aaad38dbeb Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Fri, 13 Oct 2023 23:44:16 +0000 Subject: [PATCH 27/50] [ci skip] chore(release): v0.6.0 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd319b..dc96094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.6.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.5.0...v0.6.0) (2023-10-13) + + +### Features + +* Update password ([#51](https://github.com/hawks-atlanta/proxy-python/issues/51)) ([395b44b](https://github.com/hawks-atlanta/proxy-python/commit/395b44bf9223cf1126521f80ac0ae1796d2207de)) + + + # [0.5.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.4.0...v0.5.0) (2023-10-13) @@ -29,12 +38,3 @@ -## [0.0.10](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.9...v0.0.10) (2023-10-11) - - -### Bug Fixes - -* Enable all CORS origins ([#44](https://github.com/hawks-atlanta/proxy-python/issues/44)) ([9f8ad6a](https://github.com/hawks-atlanta/proxy-python/commit/9f8ad6ae4dee09b18bd7e1f44d36f0c0ee7f4ced)) - - - diff --git a/version.json b/version.json index 381a76d..8284914 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.5.0" + "version": "0.6.0" } \ No newline at end of file From 7aca7f204ce10c37154bcf1ed716cd13ca02c142 Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Fri, 13 Oct 2023 22:16:56 -0500 Subject: [PATCH 28/50] feat: create a folder (#52) --- src/controllers/account/__init__.py | 7 ++ .../account/register_user/_handler.py | 33 ++++++ .../register_user/_register_user_test.py | 49 +++++++++ .../update_password/_handler.py} | 29 ----- .../update_password/_update_password_test.py} | 51 +-------- src/controllers/authentication/__init__.py | 4 + .../login/_handler.py} | 28 ++--- .../authentication/login/_login_test.py | 44 ++++++++ .../authentication/refresh/_handler.py | 18 +++ .../authentication/refresh/_refresh_test.py} | 41 +------ src/controllers/files/__init__.py | 9 ++ .../_check_file_status_test.py | 90 +++++++++++++++ .../files/check_file_status/_handler.py | 20 ++++ .../_create_directory_test.py | 103 ++++++++++++++++++ .../files/create_directory/_handler.py | 37 +++++++ .../rename_file/_handler.py} | 19 ---- .../files/rename_file/_rename_file_test.py} | 88 +-------------- src/views/_account_views.py | 6 +- src/views/_authentication_views.py | 6 +- src/views/_file_views.py | 13 ++- 20 files changed, 444 insertions(+), 251 deletions(-) create mode 100644 src/controllers/account/__init__.py create mode 100644 src/controllers/account/register_user/_handler.py create mode 100644 src/controllers/account/register_user/_register_user_test.py rename src/controllers/{_account_controllers.py => account/update_password/_handler.py} (52%) rename src/{views/_account_views_test.py => controllers/account/update_password/_update_password_test.py} (62%) create mode 100644 src/controllers/authentication/__init__.py rename src/controllers/{_authentication_controllers.py => authentication/login/_handler.py} (52%) create mode 100644 src/controllers/authentication/login/_login_test.py create mode 100644 src/controllers/authentication/refresh/_handler.py rename src/{views/_authentication_views_test.py => controllers/authentication/refresh/_refresh_test.py} (61%) create mode 100644 src/controllers/files/__init__.py create mode 100644 src/controllers/files/check_file_status/_check_file_status_test.py create mode 100644 src/controllers/files/check_file_status/_handler.py create mode 100644 src/controllers/files/create_directory/_create_directory_test.py create mode 100644 src/controllers/files/create_directory/_handler.py rename src/controllers/{_file_controllers.py => files/rename_file/_handler.py} (57%) rename src/{views/_file_views_test.py => controllers/files/rename_file/_rename_file_test.py} (57%) diff --git a/src/controllers/account/__init__.py b/src/controllers/account/__init__.py new file mode 100644 index 0000000..7cf62d6 --- /dev/null +++ b/src/controllers/account/__init__.py @@ -0,0 +1,7 @@ +from .register_user._handler import register_handler +from .update_password._handler import update_password_handler + +ACCOUNT_HANDLERS = { + "REGISTER": register_handler, + "UPDATE_PASSWORD": update_password_handler, +} diff --git a/src/controllers/account/register_user/_handler.py b/src/controllers/account/register_user/_handler.py new file mode 100644 index 0000000..fce158d --- /dev/null +++ b/src/controllers/account/register_user/_handler.py @@ -0,0 +1,33 @@ +import json +from flask import request +from src.config.soap_client import soap_client + + +def register_handler(): + try: + # Get JSON data from the request + data = json.loads(request.data) + username = data.get("username") + password = data.get("password") + + not_valid_username = not username or len(username) == 0 + not_valid_password = not password or len(password) == 0 + if not_valid_username or not_valid_password: + return {"msg": "Required fields are missing in JSON data"}, 400 + + result = soap_client.service.account_register( + {"username": username, "password": password} + ) + + if result.auth is not None: + jwt = result.auth.token + return {"msg": "Register succeeded", "token": jwt}, 200 + + return {"msg": "Username already registered"}, 409 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception as e: + print("[Exception] register_handler ->", str(e)) + return {"msg": "There was an error registering the user"}, 500 diff --git a/src/controllers/account/register_user/_register_user_test.py b/src/controllers/account/register_user/_register_user_test.py new file mode 100644 index 0000000..da3be2f --- /dev/null +++ b/src/controllers/account/register_user/_register_user_test.py @@ -0,0 +1,49 @@ +import json +from main import app +from src.lib.faker import fake_username, fake_password + + +register_test_data = {"username": fake_username(), "password": fake_password()} + + +def test_account_register_successful() -> None: + register_data = { + "username": register_test_data["username"], + "password": register_test_data["password"], + } + response = app.test_client().post("/account/register", json=register_data) + json_response = json.loads(response.data) + + assert response.status_code == 200 + assert json.loads(response.data)["msg"] == "Register succeeded" + assert json_response["token"] != "" + + +def test_account_register_missing_fields() -> None: + # Empty json + response = app.test_client().post("/account/register") + assert response.status_code == 400 + assert ( + json.loads(response.data)["msg"] == "Invalid JSON data provided in the request" + ) + + # Missing password + data = {"username": register_test_data["username"]} + response = app.test_client().post("/account/register", json=data) + + assert response.status_code == 400 + assert ( + json.loads(response.data)["msg"] == "Required fields are missing in JSON data" + ) + + +def test_account_register_Username_already_registered() -> None: + data = { + "username": register_test_data["username"], + "password": register_test_data["password"], + } + response = app.test_client().post("/account/register", json=data) + json_response = json.loads(response.data) + + assert response.status_code == 409 + assert json_response["msg"] == "Username already registered" diff --git a/src/controllers/_account_controllers.py b/src/controllers/account/update_password/_handler.py similarity index 52% rename from src/controllers/_account_controllers.py rename to src/controllers/account/update_password/_handler.py index 5070f3e..0a0d365 100644 --- a/src/controllers/_account_controllers.py +++ b/src/controllers/account/update_password/_handler.py @@ -3,35 +3,6 @@ from src.config.soap_client import soap_client -def register_handler(): - try: - # Get JSON data from the request - data = request.json - - if not data: - return {"msg": "No JSON data provided in the request"}, 400 - - username = data.get("username") - password = data.get("password") - - if not username or not password: - return {"msg": "Required fields are missing in JSON data"}, 400 - - result = soap_client.service.account_register( - {"username": username, "password": password} - ) - - if result.auth is not None: - jwt = result.auth.token - return {"msg": "Register succeeded", "token": jwt}, 200 - - return {"msg": "Username already registered"}, 409 - - except Exception as e: - print("[Exception] register_handler ->", str(e)) - return {"msg": "There was an error registering the user"}, 500 - - def update_password_handler(token): try: data = json.loads(request.data) diff --git a/src/views/_account_views_test.py b/src/controllers/account/update_password/_update_password_test.py similarity index 62% rename from src/views/_account_views_test.py rename to src/controllers/account/update_password/_update_password_test.py index 4304ca6..c5e4900 100644 --- a/src/views/_account_views_test.py +++ b/src/controllers/account/update_password/_update_password_test.py @@ -1,57 +1,10 @@ import json from main import app -from config.soap_client import soap_client -from src.lib.faker import fake_username, fake_password - - -# REGISTER TEST'S -register_test_data = {"username": fake_username(), "password": fake_password()} - - -def test_account_register_successful() -> None: - register_data = { - "username": register_test_data["username"], - "password": register_test_data["password"], - } - response = app.test_client().post("/account/register", json=register_data) - json_response = json.loads(response.data) - assert response.status_code == 200 - assert json.loads(response.data)["msg"] == "Register succeeded" - assert json_response["token"] != "" - - -def test_account_register_missing_fields() -> None: - # Empty json - data = {} - response = app.test_client().post("/account/register", json=data) - - assert response.status_code == 400 - assert json.loads(response.data)["msg"] == "No JSON data provided in the request" - - # Missing password - data = {"username": register_test_data["username"]} - response = app.test_client().post("/account/register", json=data) - - assert response.status_code == 400 - assert ( - json.loads(response.data)["msg"] == "Required fields are missing in JSON data" - ) - - -def test_account_register_Username_already_registered() -> None: - data = { - "username": register_test_data["username"], - "password": register_test_data["password"], - } - response = app.test_client().post("/account/register", json=data) - json_response = json.loads(response.data) - - assert response.status_code == 409 - assert json_response["msg"] == "Username already registered" +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password -# PASSWORD UPDATE TEST'S update_password_test_data = {"username": fake_username(), "password": fake_password()} diff --git a/src/controllers/authentication/__init__.py b/src/controllers/authentication/__init__.py new file mode 100644 index 0000000..93b3416 --- /dev/null +++ b/src/controllers/authentication/__init__.py @@ -0,0 +1,4 @@ +from .login._handler import login_handler +from .refresh._handler import challenge_handler + +AUTHENTICATION_HANDLERS = {"LOGIN": login_handler, "REFRESH": challenge_handler} diff --git a/src/controllers/_authentication_controllers.py b/src/controllers/authentication/login/_handler.py similarity index 52% rename from src/controllers/_authentication_controllers.py rename to src/controllers/authentication/login/_handler.py index 3a612cc..3644f07 100644 --- a/src/controllers/_authentication_controllers.py +++ b/src/controllers/authentication/login/_handler.py @@ -1,13 +1,17 @@ +import json from flask import request from src.config.soap_client import soap_client def login_handler(): try: - data = request.json + data = json.loads(request.data) username = data.get("username") password = data.get("password") - if not username or not password: + + not_valid_username = not username or len(username) == 0 + not_valid_password = not password or len(password) == 0 + if not_valid_username or not_valid_password: return {"msg": "Required fields are missing in JSON data"}, 400 result = soap_client.service.auth_login( @@ -20,23 +24,9 @@ def login_handler(): return {"msg": "Invalid credentials"}, 401 + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + except Exception as e: print("[Exception] login_handler ->", str(e)) return {"msg": "There was an error logging in"}, 500 - - -def challenge_handler(token): - try: - response = soap_client.service.auth_refresh({"token": token}) - if hasattr(response.auth, "token"): - refreshed_token = response.auth.token - return { - "msg": "JWT refreshed successfully", - "token": refreshed_token, - }, 200 - else: - return {"msg": response.msg}, response.code - - except Exception as e: - print("[Exception] challenge ->", str(e)) - return {"msg": "There was an error validating or refreshing the session"}, 500 diff --git a/src/controllers/authentication/login/_login_test.py b/src/controllers/authentication/login/_login_test.py new file mode 100644 index 0000000..082fc9e --- /dev/null +++ b/src/controllers/authentication/login/_login_test.py @@ -0,0 +1,44 @@ +import json +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + + +def test_auth_login_successful() -> None: + test_data = {"username": fake_username(), "password": fake_password()} + + registration_data = { + "username": test_data["username"], + "password": test_data["password"], + } + registration_result = soap_client.service.account_register(registration_data) + assert registration_result.error is False + + login_data = {"username": test_data["username"], "password": test_data["password"]} + response = app.test_client().post("/auth/login", json=login_data) + assert response.status_code == 200 + + json_response = json.loads(response.data) + assert json_response["msg"] != "" + assert json_response["token"] != "" + + +def test_auth_login_invalid_credentials() -> None: + data = {"username": "unexistent", "password": "password"} + response = app.test_client().post("/auth/login", json=data) + json_response = json.loads(response.data) + + assert response.status_code == 401 + assert json_response["msg"] == "Invalid credentials" + + +def test_auth_login_missing_fields() -> None: + # No JSON + response = app.test_client().post("/auth/login") + assert response.status_code == 400 + + # Missing field + data = {"username": "miguel"} + response = app.test_client().post("/auth/login", json=data) + assert response.status_code == 400 diff --git a/src/controllers/authentication/refresh/_handler.py b/src/controllers/authentication/refresh/_handler.py new file mode 100644 index 0000000..9c962ec --- /dev/null +++ b/src/controllers/authentication/refresh/_handler.py @@ -0,0 +1,18 @@ +from src.config.soap_client import soap_client + + +def challenge_handler(token): + try: + response = soap_client.service.auth_refresh({"token": token}) + if hasattr(response.auth, "token"): + refreshed_token = response.auth.token + return { + "msg": "JWT refreshed successfully", + "token": refreshed_token, + }, 200 + else: + return {"msg": response.msg}, response.code + + except Exception as e: + print("[Exception] challenge ->", str(e)) + return {"msg": "There was an error validating or refreshing the session"}, 500 diff --git a/src/views/_authentication_views_test.py b/src/controllers/authentication/refresh/_refresh_test.py similarity index 61% rename from src/views/_authentication_views_test.py rename to src/controllers/authentication/refresh/_refresh_test.py index 5b836a8..ed175b1 100644 --- a/src/views/_authentication_views_test.py +++ b/src/controllers/authentication/refresh/_refresh_test.py @@ -1,45 +1,8 @@ import json from main import app -from config.soap_client import soap_client -from src.lib.faker import fake_username, fake_password - - -def test_auth_login_successful() -> None: - test_data = {"username": fake_username(), "password": fake_password()} - - registration_data = { - "username": test_data["username"], - "password": test_data["password"], - } - registration_result = soap_client.service.account_register(registration_data) - assert registration_result.error is False - login_data = {"username": test_data["username"], "password": test_data["password"]} - response = app.test_client().post("/auth/login", json=login_data) - assert response.status_code == 200 - - json_response = json.loads(response.data) - assert json_response["msg"] != "" - assert json_response["token"] != "" - - -def test_auth_login_invalid_credentials() -> None: - data = {"username": "unexistent", "password": "password"} - response = app.test_client().post("/auth/login", json=data) - json_response = json.loads(response.data) - - assert response.status_code == 401 - assert json_response["msg"] == "Invalid credentials" - - -def test_auth_login_missing_fields() -> None: - data = {"username": "miguel"} - response = app.test_client().post("/auth/login", json=data) - assert response.status_code == 400 - - data = {"password": "miguel"} - response = app.test_client().post("/auth/login", json=data) - assert response.status_code == 400 +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password def test_auth_refresh_missing_token() -> None: diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py new file mode 100644 index 0000000..1eba6b1 --- /dev/null +++ b/src/controllers/files/__init__.py @@ -0,0 +1,9 @@ +from .check_file_status._handler import check_state_handler +from .rename_file._handler import rename_handler +from .create_directory._handler import create_new_dir_handler + +FILES_HANDLERS = { + "CHECK_STATE": check_state_handler, + "RENAME": rename_handler, + "CREATE_DIRECTORY": create_new_dir_handler, +} diff --git a/src/controllers/files/check_file_status/_check_file_status_test.py b/src/controllers/files/check_file_status/_check_file_status_test.py new file mode 100644 index 0000000..744fa56 --- /dev/null +++ b/src/controllers/files/check_file_status/_check_file_status_test.py @@ -0,0 +1,90 @@ +import json +from uuid import uuid4 +from random import randbytes +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + + +# GET STATUS TESTS +get_status_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "name": "test_file.txt", + "content": randbytes(1024), + }, +} + + +def test_get_status_not_success_code(): + # Register an user + register_response = soap_client.service.account_register( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Try to get status of the file + random_file_uuid = uuid4() + response = soap_client.service.file_check( + {"token": token, "fileUUID": random_file_uuid} + ) + + assert response.code == 404 + assert response.msg == f"There is no file with the {random_file_uuid} UUID" + + +def test_get_status_success_code(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file + upload_response = soap_client.service.file_upload( + { + "token": token, + "fileName": get_status_test_data["file"]["name"], + "fileContent": get_status_test_data["file"]["content"], + "location": None, + } + ) + assert upload_response.error is False + + # Get status of the file + response = app.test_client().get( + f"/file/{upload_response.fileUUID}/status", + headers={"Authorization": f"Bearer {token}"}, + ) + + has_expected_success_code = ( + response.status_code == 200 or response.status_code == 202 + ) + assert has_expected_success_code + + json_response = json.loads(response.data) + has_boolean_ready = ( + json_response["ready"] is True or json_response["ready"] is False + ) + assert has_boolean_ready + + assert json_response["msg"] == "File status has been obtained successfully" diff --git a/src/controllers/files/check_file_status/_handler.py b/src/controllers/files/check_file_status/_handler.py new file mode 100644 index 0000000..49696f6 --- /dev/null +++ b/src/controllers/files/check_file_status/_handler.py @@ -0,0 +1,20 @@ +from src.config.soap_client import soap_client + + +def check_state_handler(token, file_uuid): + try: + response = soap_client.service.file_check( + {"token": token, "fileUUID": file_uuid} + ) + + has_success_code = str(response.code).startswith("20") + if not has_success_code: + return {"msg": response.msg}, response.code + + return { + "msg": "File status has been obtained successfully", + "ready": response.ready, + }, response.code + except Exception as e: + print("[Exception] check_state_handler ->", str(e)) + return {"msg": "There was an error checking the file state"}, 500 diff --git a/src/controllers/files/create_directory/_create_directory_test.py b/src/controllers/files/create_directory/_create_directory_test.py new file mode 100644 index 0000000..7937f0e --- /dev/null +++ b/src/controllers/files/create_directory/_create_directory_test.py @@ -0,0 +1,103 @@ +import json +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + + +# CREATE FOLDER TEST +test_folders = { + "username": fake_username(), + "password": fake_password(), + "folder": { + "directoryName": "test-directory232", + "location": "test-location232", + "token": "test-token232", + }, +} + + +def test_createfolder_bad_request(): + # Register an user + register_response = soap_client.service.account_register( + { + "username": test_folders["username"], + "password": test_folders["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": test_folders["username"], + "password": test_folders["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # No JSON (400 Bad Request) + response = app.test_client().post( + "/folders", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + # Empty directory name (400 Bad Request) + response = app.test_client().post( + "/folders", + json={"directoryName": ""}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_createfolder_success(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": test_folders["username"], + "password": test_folders["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Create a folder + create_folder_response = app.test_client().post( + "/folders", + json={ + "directoryName": test_folders["folder"]["directoryName"], + "location": test_folders["folder"]["location"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + + assert create_folder_response.status_code == 201 + + +def test_createfolder_duplicate(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": test_folders["username"], + "password": test_folders["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Create a folder with a duplicate name + create_folder_response = app.test_client().post( + "/folders", + json={ + "directoryName": test_folders["folder"]["directoryName"], + "location": test_folders["folder"]["location"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + + # already exists + assert create_folder_response.status_code == 409 + json.loads(create_folder_response.data) diff --git a/src/controllers/files/create_directory/_handler.py b/src/controllers/files/create_directory/_handler.py new file mode 100644 index 0000000..c4e5626 --- /dev/null +++ b/src/controllers/files/create_directory/_handler.py @@ -0,0 +1,37 @@ +import json +from flask import request +from src.config.soap_client import soap_client + + +def create_new_dir_handler(token): + try: + data = json.loads(request.data) + + directoryName = data.get("directoryName") + location = data.get("location") + + if not directoryName or not location: + return {"msg": "Required fields are missing in JSON data"}, 400 + + response = soap_client.service.file_new_dir( + { + "directoryName": directoryName, + "location": location, + "token": token, + } + ) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return { + "msg": "New directory created successfully", + "directoryUUID": response["fileUUID"], + }, response["code"] + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception as e: + print("[Exception] create_new_directory ->", str(e)) + return {"msg": "There was an error creating the new directory"}, 500 diff --git a/src/controllers/_file_controllers.py b/src/controllers/files/rename_file/_handler.py similarity index 57% rename from src/controllers/_file_controllers.py rename to src/controllers/files/rename_file/_handler.py index 5aad7f0..0ef312a 100644 --- a/src/controllers/_file_controllers.py +++ b/src/controllers/files/rename_file/_handler.py @@ -3,25 +3,6 @@ from src.config.soap_client import soap_client -def check_state_handler(token, file_uuid): - try: - response = soap_client.service.file_check( - {"token": token, "fileUUID": file_uuid} - ) - - has_success_code = str(response.code).startswith("20") - if not has_success_code: - return {"msg": response.msg}, response.code - - return { - "msg": "File status has been obtained successfully", - "ready": response.ready, - }, response.code - except Exception as e: - print("[Exception] check_state_handler ->", str(e)) - return {"msg": "There was an error checking the file state"}, 500 - - def rename_handler(token, file_uuid): try: data = json.loads(request.data) diff --git a/src/views/_file_views_test.py b/src/controllers/files/rename_file/_rename_file_test.py similarity index 57% rename from src/views/_file_views_test.py rename to src/controllers/files/rename_file/_rename_file_test.py index 25754e5..2a4709f 100644 --- a/src/views/_file_views_test.py +++ b/src/controllers/files/rename_file/_rename_file_test.py @@ -1,93 +1,9 @@ import json -from uuid import uuid4 from random import randbytes from main import app -from config.soap_client import soap_client -from src.lib.faker import fake_username, fake_password - - -# GET STATUS TESTS -get_status_test_data = { - "username": fake_username(), - "password": fake_password(), - "file": { - "name": "test_file.txt", - "content": randbytes(1024), - }, -} - - -def test_get_status_not_success_code(): - # Register an user - register_response = soap_client.service.account_register( - { - "username": get_status_test_data["username"], - "password": get_status_test_data["password"], - } - ) - assert register_response.error is False - - # Login with the user - login_response = soap_client.service.auth_login( - { - "username": get_status_test_data["username"], - "password": get_status_test_data["password"], - } - ) - assert login_response.error is False - token = login_response.auth.token - - # Try to get status of the file - random_file_uuid = uuid4() - response = soap_client.service.file_check( - {"token": token, "fileUUID": random_file_uuid} - ) - - assert response.code == 404 - assert response.msg == f"There is no file with the {random_file_uuid} UUID" - - -def test_get_status_success_code(): - # Login with the user - login_response = soap_client.service.auth_login( - { - "username": get_status_test_data["username"], - "password": get_status_test_data["password"], - } - ) - assert login_response.error is False - token = login_response.auth.token - - # Upload a file - upload_response = soap_client.service.file_upload( - { - "token": token, - "fileName": get_status_test_data["file"]["name"], - "fileContent": get_status_test_data["file"]["content"], - "location": None, - } - ) - assert upload_response.error is False - - # Get status of the file - response = app.test_client().get( - f"/file/{upload_response.fileUUID}/status", - headers={"Authorization": f"Bearer {token}"}, - ) - - has_expected_success_code = ( - response.status_code == 200 or response.status_code == 202 - ) - assert has_expected_success_code - - json_response = json.loads(response.data) - has_boolean_ready = ( - json_response["ready"] is True or json_response["ready"] is False - ) - assert has_boolean_ready - - assert json_response["msg"] == "File status has been obtained successfully" +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password # RENAME FILE TESTS rename_test_data = { diff --git a/src/views/_account_views.py b/src/views/_account_views.py index 20a72c8..7c50f60 100644 --- a/src/views/_account_views.py +++ b/src/views/_account_views.py @@ -1,16 +1,16 @@ import flask from src.middlewares import auth_middlewares -from src.controllers import _account_controllers +from src.controllers.account import ACCOUNT_HANDLERS views = flask.Blueprint("account", __name__) @views.route("/account/register", methods=["POST"]) def account_register(): - return _account_controllers.register_handler() + return ACCOUNT_HANDLERS["REGISTER"]() @views.route("/account/password", methods=["PATCH"]) @auth_middlewares.token_required def account_password(token): - return _account_controllers.update_password_handler(token) + return ACCOUNT_HANDLERS["UPDATE_PASSWORD"](token) diff --git a/src/views/_authentication_views.py b/src/views/_authentication_views.py index eb278bc..9f2fd43 100644 --- a/src/views/_authentication_views.py +++ b/src/views/_authentication_views.py @@ -1,16 +1,16 @@ import flask -from src.controllers import _authentication_controllers from src.middlewares import auth_middlewares +from src.controllers.authentication import AUTHENTICATION_HANDLERS views = flask.Blueprint("authentication", __name__) @views.route("/auth/login", methods=["POST"]) def auth_login(): - return _authentication_controllers.login_handler() + return AUTHENTICATION_HANDLERS["LOGIN"]() @views.route("/auth/refresh", methods=["POST"]) @auth_middlewares.token_required def auth_refresh(token): - return _authentication_controllers.challenge_handler(token) + return AUTHENTICATION_HANDLERS["REFRESH"](token) diff --git a/src/views/_file_views.py b/src/views/_file_views.py index d99aa75..ca9e9c2 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -1,7 +1,6 @@ import flask from src.middlewares import auth_middlewares -from src.controllers import _file_controllers - +from src.controllers.files import FILES_HANDLERS views = flask.Blueprint("file", __name__) @@ -9,10 +8,16 @@ @views.route("/file//status", methods=["GET"]) @auth_middlewares.token_required def file_check(token, file_uuid): - return _file_controllers.check_state_handler(token, file_uuid) + return FILES_HANDLERS["CHECK_STATE"](token, file_uuid) @views.route("/file//rename", methods=["PATCH"]) @auth_middlewares.token_required def file_rename(token, file_uuid): - return _file_controllers.rename_handler(token, file_uuid) + return FILES_HANDLERS["RENAME"](token, file_uuid) + + +@views.route("/folders", methods=["POST"]) +@auth_middlewares.token_required +def dir_create(token): + return FILES_HANDLERS["CREATE_DIRECTORY"](token) From 3c408a9ad3c0285c7c45473d4c3f1fe785d62cc1 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sat, 14 Oct 2023 03:17:13 +0000 Subject: [PATCH 29/50] [ci skip] chore(release): v0.7.0 [skip ci] --- CHANGELOG.md | 13 +++++++++---- version.json | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc96094..c5d0a77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.7.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.6.0...v0.7.0) (2023-10-14) + + +### Features + +* create a folder ([#52](https://github.com/hawks-atlanta/proxy-python/issues/52)) ([7aca7f2](https://github.com/hawks-atlanta/proxy-python/commit/7aca7f204ce10c37154bcf1ed716cd13ca02c142)) + + + # [0.6.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.5.0...v0.6.0) (2023-10-13) @@ -34,7 +43,3 @@ -## [0.0.11](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.10...v0.0.11) (2023-10-11) - - - diff --git a/version.json b/version.json index 8284914..17aba91 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.6.0" + "version": "0.7.0" } \ No newline at end of file From 097d0267b4596ee8cea5f24e4b4ceaf019ff801d Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Sat, 14 Oct 2023 09:23:18 -0500 Subject: [PATCH 30/50] feat: file upload (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: file upload endpoint. Start of file upload endpoint , testing is still pending, in addition to possible changes according to the API update * feat: Progress and small corrections in upload_endpoint Progress is made in the endpoint in addition to correcting syntax and .py file creation problems * fix: upload file endpont corrections Corrections are made to the endpoint upload_file according to API update * fix: error message * fix: error messages in upload controller * feat: Create helper function to check if an UUID is valid * fix(files): Use multipart form data instead of JSON * test(files): Add success case test to the endpoint to upload files --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> Co-authored-by: Pedro Andrés Chaparro Quintero --- src/controllers/files/__init__.py | 2 + src/controllers/files/upload_file/_handler.py | 49 +++++++++++++++ .../files/upload_file/_upload_file_test.py | 59 +++++++++++++++++++ src/lib/helpers.py | 10 ++++ src/views/_file_views.py | 7 +++ 5 files changed, 127 insertions(+) create mode 100644 src/controllers/files/upload_file/_handler.py create mode 100644 src/controllers/files/upload_file/_upload_file_test.py create mode 100644 src/lib/helpers.py diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index 1eba6b1..8eb4833 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -1,9 +1,11 @@ from .check_file_status._handler import check_state_handler from .rename_file._handler import rename_handler from .create_directory._handler import create_new_dir_handler +from .upload_file._handler import upload_file_handler FILES_HANDLERS = { "CHECK_STATE": check_state_handler, "RENAME": rename_handler, "CREATE_DIRECTORY": create_new_dir_handler, + "UPLOAD": upload_file_handler, } diff --git a/src/controllers/files/upload_file/_handler.py b/src/controllers/files/upload_file/_handler.py new file mode 100644 index 0000000..0be1c0e --- /dev/null +++ b/src/controllers/files/upload_file/_handler.py @@ -0,0 +1,49 @@ +from flask import request +import os +from werkzeug.utils import secure_filename +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def upload_file_handler(token): + try: + if "file" not in request.files or "location" not in request.form: + return {"msg": "Required fields are missing in form data"}, 400 + + # Get file from request + file = request.files["file"] + + # Set null if file location is am empty string + fileLocation = request.form["location"] + fileLocation = None if fileLocation == "" else fileLocation + + # Check if fileLocation is a valid UUID + not_valid_location = fileLocation is not None and not is_valid_uuid( + fileLocation + ) + if not_valid_location: + return {"msg": "Not valid file location provided"}, 400 + + # Separate file name from extension + fileName = os.path.splitext(secure_filename(file.filename))[0] + result = soap_client.service.file_upload( + { + "fileName": fileName, + "fileContent": file.read(), + "location": fileLocation, + "token": token, + } + ) + + if result.fileUUID is None: + return {"msg": result["msg"]}, result["code"] + + fileId = result.fileUUID + return {"msg": "The file is being uploaded", "fileUUID": fileId}, 201 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception as e: + print("[Exception] file_download_handler ->", str(e)) + return {"msg": "Internal error", "error": str(e)}, 500 diff --git a/src/controllers/files/upload_file/_upload_file_test.py b/src/controllers/files/upload_file/_upload_file_test.py new file mode 100644 index 0000000..f59e1a1 --- /dev/null +++ b/src/controllers/files/upload_file/_upload_file_test.py @@ -0,0 +1,59 @@ +import json +import os +from random import randbytes +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +upload_file_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "name": "picture", + "content": randbytes(1024), + }, +} + + +def test_file_upload_successful() -> None: + # Register an user + register_response = soap_client.service.account_register( + { + "username": upload_file_test_data["username"], + "password": upload_file_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": upload_file_test_data["username"], + "password": upload_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Write random file to disk + with open(f"./{upload_file_test_data['file']['name']}.jpeg", "wb") as rf: + rf.write(upload_file_test_data["file"]["content"]) + + # Upload file + response = app.test_client().post( + "/file/upload", + headers={"Authorization": f"Bearer {token}"}, + data={ + "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "location": "", + }, + ) + json_response = json.loads(response.data) + + assert response.status_code == 201 + assert json_response["msg"] == "The file is being uploaded" + assert json_response["fileUUID"] is not None + + # Delete file from disk + os.remove(f"./{upload_file_test_data['file']['name']}.jpeg") diff --git a/src/lib/helpers.py b/src/lib/helpers.py new file mode 100644 index 0000000..60d2f1d --- /dev/null +++ b/src/lib/helpers.py @@ -0,0 +1,10 @@ +from uuid import UUID + + +def is_valid_uuid(uuid_to_test): + try: + uuid_obj = UUID(uuid_to_test, version=4) + except ValueError: + return False + + return str(uuid_obj) == uuid_to_test diff --git a/src/views/_file_views.py b/src/views/_file_views.py index ca9e9c2..37eec16 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -2,6 +2,7 @@ from src.middlewares import auth_middlewares from src.controllers.files import FILES_HANDLERS + views = flask.Blueprint("file", __name__) @@ -21,3 +22,9 @@ def file_rename(token, file_uuid): @auth_middlewares.token_required def dir_create(token): return FILES_HANDLERS["CREATE_DIRECTORY"](token) + + +@views.route("/file/upload", methods=["POST"]) +@auth_middlewares.token_required +def file_upload(token): + return FILES_HANDLERS["UPLOAD"](token) From f3357082e9edf2b8f62205fa5878e997c8a898b9 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sat, 14 Oct 2023 14:26:04 +0000 Subject: [PATCH 31/50] [ci skip] chore(release): v0.8.0 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d0a77..d5497db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.8.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.7.0...v0.8.0) (2023-10-14) + + +### Features + +* file upload ([#53](https://github.com/hawks-atlanta/proxy-python/issues/53)) ([097d026](https://github.com/hawks-atlanta/proxy-python/commit/097d0267b4596ee8cea5f24e4b4ceaf019ff801d)) + + + # [0.7.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.6.0...v0.7.0) (2023-10-14) @@ -34,12 +43,3 @@ -# [0.1.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.0.11...v0.1.0) (2023-10-12) - - -### Features - -* update docs openapi ([#41](https://github.com/hawks-atlanta/proxy-python/issues/41)) ([9dc6959](https://github.com/hawks-atlanta/proxy-python/commit/9dc695992b954c02ef84600b2df4d8d89e39d489)) - - - diff --git a/version.json b/version.json index 17aba91..cf94925 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.7.0" + "version": "0.8.0" } \ No newline at end of file From 624cc06af6443602bc9b946c8fc7ee5376dbea2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sat, 14 Oct 2023 13:08:13 -0500 Subject: [PATCH 32/50] fix(files): Keep files extension (#56) --- src/controllers/files/rename_file/_rename_file_test.py | 4 ++-- src/controllers/files/upload_file/_handler.py | 3 +-- src/controllers/files/upload_file/_upload_file_test.py | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/controllers/files/rename_file/_rename_file_test.py b/src/controllers/files/rename_file/_rename_file_test.py index 2a4709f..69c4686 100644 --- a/src/controllers/files/rename_file/_rename_file_test.py +++ b/src/controllers/files/rename_file/_rename_file_test.py @@ -11,7 +11,7 @@ "password": fake_password(), "file": { "uuid": None, - "name": "test-file", + "name": "test.txt", "content": randbytes(1024), }, } @@ -76,7 +76,7 @@ def test_rename_success(): assert create_file_response.error is False # Rename the file - new_name = "renamed-test-file" + new_name = "renamed.txt" response = app.test_client().patch( f"/file/{create_file_response.fileUUID}/rename", json={"newName": new_name}, diff --git a/src/controllers/files/upload_file/_handler.py b/src/controllers/files/upload_file/_handler.py index 0be1c0e..b7ec7c8 100644 --- a/src/controllers/files/upload_file/_handler.py +++ b/src/controllers/files/upload_file/_handler.py @@ -1,5 +1,4 @@ from flask import request -import os from werkzeug.utils import secure_filename from src.config.soap_client import soap_client from src.lib.helpers import is_valid_uuid @@ -25,7 +24,7 @@ def upload_file_handler(token): return {"msg": "Not valid file location provided"}, 400 # Separate file name from extension - fileName = os.path.splitext(secure_filename(file.filename))[0] + fileName = secure_filename(file.filename) result = soap_client.service.file_upload( { "fileName": fileName, diff --git a/src/controllers/files/upload_file/_upload_file_test.py b/src/controllers/files/upload_file/_upload_file_test.py index f59e1a1..1b5e251 100644 --- a/src/controllers/files/upload_file/_upload_file_test.py +++ b/src/controllers/files/upload_file/_upload_file_test.py @@ -10,7 +10,7 @@ "username": fake_username(), "password": fake_password(), "file": { - "name": "picture", + "name": "picture.jpeg", "content": randbytes(1024), }, } @@ -37,7 +37,7 @@ def test_file_upload_successful() -> None: token = login_response.auth.token # Write random file to disk - with open(f"./{upload_file_test_data['file']['name']}.jpeg", "wb") as rf: + with open(f"./{upload_file_test_data['file']['name']}", "wb") as rf: rf.write(upload_file_test_data["file"]["content"]) # Upload file @@ -45,7 +45,7 @@ def test_file_upload_successful() -> None: "/file/upload", headers={"Authorization": f"Bearer {token}"}, data={ - "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "file": (open(f"./{upload_file_test_data['file']['name']}", "rb")), "location": "", }, ) @@ -56,4 +56,4 @@ def test_file_upload_successful() -> None: assert json_response["fileUUID"] is not None # Delete file from disk - os.remove(f"./{upload_file_test_data['file']['name']}.jpeg") + os.remove(f"./{upload_file_test_data['file']['name']}") From 0c7acae97b6786c47024e79d01461a3569f05908 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sat, 14 Oct 2023 18:09:15 +0000 Subject: [PATCH 33/50] [ci skip] chore(release): v0.8.1 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5497db..a80a4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.8.1](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.0...v0.8.1) (2023-10-14) + + +### Bug Fixes + +* **files:** Keep files extension ([#56](https://github.com/hawks-atlanta/proxy-python/issues/56)) ([624cc06](https://github.com/hawks-atlanta/proxy-python/commit/624cc06af6443602bc9b946c8fc7ee5376dbea2c)) + + + # [0.8.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.7.0...v0.8.0) (2023-10-14) @@ -34,12 +43,3 @@ -# [0.4.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.1.0...v0.4.0) (2023-10-12) - - -### Features - -* Endpoint to get the file status ([#49](https://github.com/hawks-atlanta/proxy-python/issues/49)) ([af95070](https://github.com/hawks-atlanta/proxy-python/commit/af9507083f7b2b5a833cb0ac50c865806778ca5d)) - - - diff --git a/version.json b/version.json index cf94925..4069080 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.8.0" + "version": "0.8.1" } \ No newline at end of file From 4de7711b01065e0075552c0810ed255b8ae6b8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:57:15 -0500 Subject: [PATCH 34/50] test: Increment save file endpoint coverage (#54) * refactor(files): Remove unreachable block Since JSON is not being used in the endpoint, the block is unreachable * test(files): Increment upload handler coverage to 88% All lines, except the `500` status code error, are covered now --- src/controllers/files/upload_file/_handler.py | 3 - .../files/upload_file/_upload_file_test.py | 62 ++++++++++++++++++- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/controllers/files/upload_file/_handler.py b/src/controllers/files/upload_file/_handler.py index b7ec7c8..2ac618e 100644 --- a/src/controllers/files/upload_file/_handler.py +++ b/src/controllers/files/upload_file/_handler.py @@ -40,9 +40,6 @@ def upload_file_handler(token): fileId = result.fileUUID return {"msg": "The file is being uploaded", "fileUUID": fileId}, 201 - except ValueError: - return {"msg": "Invalid JSON data provided in the request"}, 400 - except Exception as e: print("[Exception] file_download_handler ->", str(e)) return {"msg": "Internal error", "error": str(e)}, 500 diff --git a/src/controllers/files/upload_file/_upload_file_test.py b/src/controllers/files/upload_file/_upload_file_test.py index 1b5e251..7c03460 100644 --- a/src/controllers/files/upload_file/_upload_file_test.py +++ b/src/controllers/files/upload_file/_upload_file_test.py @@ -16,7 +16,7 @@ } -def test_file_upload_successful() -> None: +def test_file_upload_bad_request() -> None: # Register an user register_response = soap_client.service.account_register( { @@ -40,6 +40,43 @@ def test_file_upload_successful() -> None: with open(f"./{upload_file_test_data['file']['name']}", "wb") as rf: rf.write(upload_file_test_data["file"]["content"]) + # Upload file with empty form data + response = app.test_client().post( + "/file/upload", + headers={"Authorization": f"Bearer {token}"}, + data={}, + ) + json_response = json.loads(response.data) + + assert response.status_code == 400 + assert json_response["msg"] == "Required fields are missing in form data" + + # Upload file with not valid location UUID + response = app.test_client().post( + "/file/upload", + headers={"Authorization": f"Bearer {token}"}, + data={ + "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "location": "not-valid-uuid", + }, + ) + json_response = json.loads(response.data) + + assert response.status_code == 400 + assert json_response["msg"] == "Not valid file location provided" + + +def test_file_upload_successful() -> None: + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": upload_file_test_data["username"], + "password": upload_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + # Upload file response = app.test_client().post( "/file/upload", @@ -55,5 +92,28 @@ def test_file_upload_successful() -> None: assert json_response["msg"] == "The file is being uploaded" assert json_response["fileUUID"] is not None + +def test_file_upload_conflict() -> None: + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": upload_file_test_data["username"], + "password": upload_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload file + response = app.test_client().post( + "/file/upload", + headers={"Authorization": f"Bearer {token}"}, + data={ + "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "location": "", + }, + ) + assert response.status_code == 409 + # Delete file from disk os.remove(f"./{upload_file_test_data['file']['name']}") From c30a7332aee8e6ad0aa2c2517b3b750d39ae79d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sat, 14 Oct 2023 18:32:34 -0500 Subject: [PATCH 35/50] test(files): Fix broken tests related to file upload (#58) --- src/controllers/files/upload_file/_upload_file_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/files/upload_file/_upload_file_test.py b/src/controllers/files/upload_file/_upload_file_test.py index 7c03460..dd2f51c 100644 --- a/src/controllers/files/upload_file/_upload_file_test.py +++ b/src/controllers/files/upload_file/_upload_file_test.py @@ -56,7 +56,7 @@ def test_file_upload_bad_request() -> None: "/file/upload", headers={"Authorization": f"Bearer {token}"}, data={ - "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "file": (open(f"./{upload_file_test_data['file']['name']}", "rb")), "location": "not-valid-uuid", }, ) @@ -109,7 +109,7 @@ def test_file_upload_conflict() -> None: "/file/upload", headers={"Authorization": f"Bearer {token}"}, data={ - "file": (open(f"./{upload_file_test_data['file']['name']}.jpeg", "rb")), + "file": (open(f"./{upload_file_test_data['file']['name']}", "rb")), "location": "", }, ) From 7c41c70445858a3e46e162d59692605375a2ec42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sun, 15 Oct 2023 13:37:00 -0500 Subject: [PATCH 36/50] fix: Add extra validations for UUIDs (#65) * fix(files): Update endpoint to create directories * fix(files): Add extra validations for UUIDs --- .../_check_file_status_test.py | 21 ++++++- .../files/check_file_status/_handler.py | 5 ++ .../_create_directory_test.py | 61 +++++++++++++++++-- .../files/create_directory/_handler.py | 8 ++- src/controllers/files/rename_file/_handler.py | 8 +++ .../files/rename_file/_rename_file_test.py | 8 +++ 6 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/controllers/files/check_file_status/_check_file_status_test.py b/src/controllers/files/check_file_status/_check_file_status_test.py index 744fa56..4bba038 100644 --- a/src/controllers/files/check_file_status/_check_file_status_test.py +++ b/src/controllers/files/check_file_status/_check_file_status_test.py @@ -18,7 +18,7 @@ } -def test_get_status_not_success_code(): +def test_get_status_bad_request(): # Register an user register_response = soap_client.service.account_register( { @@ -38,6 +38,25 @@ def test_get_status_not_success_code(): assert login_response.error is False token = login_response.auth.token + # Not valid file UUID (400 Bad Request) + response = app.test_client().get( + "/file/1234/status", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_get_status_not_success_code(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_status_test_data["username"], + "password": get_status_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + # Try to get status of the file random_file_uuid = uuid4() response = soap_client.service.file_check( diff --git a/src/controllers/files/check_file_status/_handler.py b/src/controllers/files/check_file_status/_handler.py index 49696f6..36cbc8d 100644 --- a/src/controllers/files/check_file_status/_handler.py +++ b/src/controllers/files/check_file_status/_handler.py @@ -1,8 +1,13 @@ from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid def check_state_handler(token, file_uuid): try: + not_valid_file_uuid = not file_uuid or not is_valid_uuid(file_uuid) + if not_valid_file_uuid: + return {"msg": "Not valid file UUID provided"}, 400 + response = soap_client.service.file_check( {"token": token, "fileUUID": file_uuid} ) diff --git a/src/controllers/files/create_directory/_create_directory_test.py b/src/controllers/files/create_directory/_create_directory_test.py index 7937f0e..e2c7be3 100644 --- a/src/controllers/files/create_directory/_create_directory_test.py +++ b/src/controllers/files/create_directory/_create_directory_test.py @@ -1,6 +1,6 @@ import json -from main import app +from main import app from src.config.soap_client import soap_client from src.lib.faker import fake_username, fake_password @@ -10,8 +10,9 @@ "username": fake_username(), "password": fake_password(), "folder": { + "uuid": None, "directoryName": "test-directory232", - "location": "test-location232", + "location": None, "token": "test-token232", }, } @@ -52,8 +53,19 @@ def test_createfolder_bad_request(): ) assert response.status_code == 400 + # Not valid location (400 Bad Request) + response = app.test_client().post( + "/folders", + json={ + "directoryName": test_folders["folder"]["directoryName"], + "location": "not-valid-uuid", + }, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + -def test_createfolder_success(): +def test_create_folder_in_root_success(): # Login with the user login_response = soap_client.service.auth_login( { @@ -73,11 +85,13 @@ def test_createfolder_success(): }, headers={"Authorization": f"Bearer {token}"}, ) + create_folder_json_response = json.loads(create_folder_response.data) assert create_folder_response.status_code == 201 + test_folders["folder"]["uuid"] = create_folder_json_response["directoryUUID"] -def test_createfolder_duplicate(): +def test_create_folder_in_folder_success(): # Login with the user login_response = soap_client.service.auth_login( { @@ -88,7 +102,31 @@ def test_createfolder_duplicate(): assert login_response.error is False token = login_response.auth.token - # Create a folder with a duplicate name + # Create a folder + create_folder_response = app.test_client().post( + "/folders", + json={ + "directoryName": test_folders["folder"]["directoryName"], + "location": test_folders["folder"]["uuid"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + + assert create_folder_response.status_code == 201 + + +def test_create_folder_duplicate(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": test_folders["username"], + "password": test_folders["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Create a folder in root with same name create_folder_response = app.test_client().post( "/folders", json={ @@ -100,4 +138,15 @@ def test_createfolder_duplicate(): # already exists assert create_folder_response.status_code == 409 - json.loads(create_folder_response.data) + + # Create a folder in folder with same name + create_folder_response = app.test_client().post( + "/folders", + json={ + "directoryName": test_folders["folder"]["directoryName"], + "location": test_folders["folder"]["uuid"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + + assert create_folder_response.status_code == 409 diff --git a/src/controllers/files/create_directory/_handler.py b/src/controllers/files/create_directory/_handler.py index c4e5626..c791e61 100644 --- a/src/controllers/files/create_directory/_handler.py +++ b/src/controllers/files/create_directory/_handler.py @@ -1,18 +1,22 @@ import json from flask import request from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid def create_new_dir_handler(token): try: data = json.loads(request.data) - directoryName = data.get("directoryName") location = data.get("location") - if not directoryName or not location: + if not directoryName: return {"msg": "Required fields are missing in JSON data"}, 400 + not_valid_location = location is not None and not is_valid_uuid(location) + if not_valid_location: + return {"msg": "Not valid location provided"}, 400 + response = soap_client.service.file_new_dir( { "directoryName": directoryName, diff --git a/src/controllers/files/rename_file/_handler.py b/src/controllers/files/rename_file/_handler.py index 0ef312a..2383505 100644 --- a/src/controllers/files/rename_file/_handler.py +++ b/src/controllers/files/rename_file/_handler.py @@ -1,15 +1,23 @@ import json from flask import request from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid def rename_handler(token, file_uuid): try: data = json.loads(request.data) + + # Validate new name new_name = data["newName"] if not new_name or len(new_name) == 0: return {"msg": "New name is required"}, 400 + # Validate file UUID + not_valid_file_uuid = not file_uuid or not is_valid_uuid(file_uuid) + if not_valid_file_uuid: + return {"msg": "Not valid file UUID provided"}, 400 + request_data = {"token": token, "fileUUID": file_uuid, "newName": new_name} response = soap_client.service.file_rename(request_data) diff --git a/src/controllers/files/rename_file/_rename_file_test.py b/src/controllers/files/rename_file/_rename_file_test.py index 69c4686..a284c1f 100644 --- a/src/controllers/files/rename_file/_rename_file_test.py +++ b/src/controllers/files/rename_file/_rename_file_test.py @@ -52,6 +52,14 @@ def test_rename_bad_request(): ) assert response.status_code == 400 + # Not valid file UUID + response = app.test_client().patch( + "/file/1234/rename", + json={"newName": "test.txt"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + def test_rename_success(): # Login with the user From 4b262390b3934da546e6c2bcb2493155fee603ba Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sun, 15 Oct 2023 18:37:17 +0000 Subject: [PATCH 37/50] [ci skip] chore(release): v0.8.2 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a80a4f6..0b00ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.8.2](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.1...v0.8.2) (2023-10-15) + + +### Bug Fixes + +* Add extra validations for UUIDs ([#65](https://github.com/hawks-atlanta/proxy-python/issues/65)) ([7c41c70](https://github.com/hawks-atlanta/proxy-python/commit/7c41c70445858a3e46e162d59692605375a2ec42)) + + + ## [0.8.1](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.0...v0.8.1) (2023-10-14) @@ -34,12 +43,3 @@ -# [0.5.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.4.0...v0.5.0) (2023-10-13) - - -### Features - -* Endpoint to rename a file ([#50](https://github.com/hawks-atlanta/proxy-python/issues/50)) ([b1e93fc](https://github.com/hawks-atlanta/proxy-python/commit/b1e93fc468187e15341161c3db1084559b64e93c)) - - - diff --git a/version.json b/version.json index 4069080..f3d94d1 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.8.1" + "version": "0.8.2" } \ No newline at end of file From a6f7074c3e69edda8bc3c91c370b917c14cc3cc6 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Sun, 15 Oct 2023 15:53:56 -0500 Subject: [PATCH 38/50] feat: share a file with another user (#61) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: share a file with another user Endpoint to share files with another user who is registered * fix: fixed integration issues. * refactor(files): Add extra validations to the share endpoint * test(files): Add missing test cases to the endpoint to share a file File is fully covered (87%) except for the internal server error response --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> Co-authored-by: Pedro Andrés Chaparro Quintero --- src/controllers/files/__init__.py | 2 + src/controllers/files/share_file/_handler.py | 39 +++++ .../files/share_file/_share_file_test.py | 139 ++++++++++++++++++ src/views/_file_views.py | 6 + 4 files changed, 186 insertions(+) create mode 100644 src/controllers/files/share_file/_handler.py create mode 100644 src/controllers/files/share_file/_share_file_test.py diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index 8eb4833..41599fd 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -2,10 +2,12 @@ from .rename_file._handler import rename_handler from .create_directory._handler import create_new_dir_handler from .upload_file._handler import upload_file_handler +from .share_file._handler import share_handler FILES_HANDLERS = { "CHECK_STATE": check_state_handler, "RENAME": rename_handler, "CREATE_DIRECTORY": create_new_dir_handler, "UPLOAD": upload_file_handler, + "SHARE": share_handler, } diff --git a/src/controllers/files/share_file/_handler.py b/src/controllers/files/share_file/_handler.py new file mode 100644 index 0000000..ca60aae --- /dev/null +++ b/src/controllers/files/share_file/_handler.py @@ -0,0 +1,39 @@ +import json +from flask import request +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def share_handler(token): + try: + data = json.loads(request.data) + fileUUID = data["fileUUID"] + otherUsername = data["otherUsername"] + + # Check if required fields are empty + empty_file_uuid = not fileUUID or len(fileUUID) == 0 + empty_other_username = not otherUsername or len(otherUsername) == 0 + if empty_file_uuid or empty_other_username: + return {"msg": "Required fields are missing in form data"}, 400 + + # Check if file UUID is valid + if not is_valid_uuid(fileUUID): + return {"msg": "Not valid file UUID provided"}, 400 + + request_data = { + "fileUUID": fileUUID, + "otherUsername": otherUsername, + "token": token, + } + response = soap_client.service.share_file(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return {"msg": "File shared successfully"}, 200 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception: + return {"msg": "There was an error sharing the file"}, 500 diff --git a/src/controllers/files/share_file/_share_file_test.py b/src/controllers/files/share_file/_share_file_test.py new file mode 100644 index 0000000..e77c09a --- /dev/null +++ b/src/controllers/files/share_file/_share_file_test.py @@ -0,0 +1,139 @@ +from random import randbytes +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +# SHARE FILE TESTS +share_test_data = { + "username": fake_username(), + "password": fake_password(), + "otherUsername": fake_username(), + "otherPassword": fake_password(), + "file": { + "uuid": None, + "name": "picture.jpeg", + "content": randbytes(1024), + }, +} + + +def test_share_bad_request(): + # Register an user + register_response1 = soap_client.service.account_register( + { + "username": share_test_data["username"], + "password": share_test_data["password"], + } + ) + assert register_response1.error is False + + # Login with the one user + login_response = soap_client.service.auth_login( + { + "username": share_test_data["username"], + "password": share_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # No token + response = app.test_client().post( + "file/share", + ) + assert response.status_code == 401 + + # Empty fields + response = app.test_client().post( + "file/share", + json={"fileUUID": "", "otherUsername": "username123"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + # Not valid UUID + response = app.test_client().post( + "file/share", + json={"fileUUID": "not-valid-uuid", "otherUsername": "username123"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_share_forbidden(): + # Login as the first user + login_response = soap_client.service.auth_login( + { + "username": share_test_data["username"], + "password": share_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file as the first user + upload_response = soap_client.service.file_upload( + { + "token": token, + "fileName": share_test_data["file"]["name"], + "fileContent": share_test_data["file"]["content"], + "location": None, + } + ) + assert upload_response.error is False + share_test_data["file"]["uuid"] = upload_response.fileUUID + + # Register the second user + register_response2 = soap_client.service.account_register( + { + "username": share_test_data["otherUsername"], + "password": share_test_data["otherPassword"], + } + ) + assert register_response2.error is False + + # Login as the second user + login_response = soap_client.service.auth_login( + { + "username": share_test_data["otherUsername"], + "password": share_test_data["otherPassword"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Try to share the file as the second user + response = app.test_client().post( + "file/share", + json={ + "fileUUID": share_test_data["file"]["uuid"], + "otherUsername": share_test_data["username"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 403 + + +def test_share_success(): + # Login as the first user + login_response = soap_client.service.auth_login( + { + "username": share_test_data["username"], + "password": share_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Share the file as the first user + response = app.test_client().post( + "file/share", + json={ + "fileUUID": share_test_data["file"]["uuid"], + "otherUsername": share_test_data["otherUsername"], + }, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + assert response.json["msg"] == "File shared successfully" diff --git a/src/views/_file_views.py b/src/views/_file_views.py index 37eec16..2d59c07 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -28,3 +28,9 @@ def dir_create(token): @auth_middlewares.token_required def file_upload(token): return FILES_HANDLERS["UPLOAD"](token) + + +@views.route("/file/share", methods=["POST"]) +@auth_middlewares.token_required +def file_share(token): + return FILES_HANDLERS["SHARE"](token) From 9bf0cdb8b7d4901fdcad1991c4a88766bf1a3bf2 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Sun, 15 Oct 2023 16:16:58 -0500 Subject: [PATCH 39/50] feat: download file (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: download file endpont folder creation: download_file download_file contains the .py files _handler.py and _download_files_test, at the moment there is no test. Additionally, the path is added to the _file_views.py file. * fix: import but unused Fixed check-linter errors where unused imports were found * feat(files): Return file instead of JSON when downloading a file According to the openapi spec, the download endpoint should return the file itself * test(files): Update tests to download a file * fix: invalid except valueError Removal of unnecessary exception * style(files): Format views file --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> Co-authored-by: Pedro Andrés Chaparro Quintero Co-authored-by: Pedro Andrés Chaparro Quintero <62714297+PedroChaparro@users.noreply.github.com> --- src/controllers/files/__init__.py | 3 + .../download_file/_download_file_test.py | 87 +++++++++++++++++++ .../files/download_file/_handler.py | 25 ++++++ src/views/_file_views.py | 6 ++ 4 files changed, 121 insertions(+) create mode 100644 src/controllers/files/download_file/_download_file_test.py create mode 100644 src/controllers/files/download_file/_handler.py diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index 41599fd..e6705c1 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -2,6 +2,8 @@ from .rename_file._handler import rename_handler from .create_directory._handler import create_new_dir_handler from .upload_file._handler import upload_file_handler + +from .download_file._handler import download_file_handler from .share_file._handler import share_handler FILES_HANDLERS = { @@ -9,5 +11,6 @@ "RENAME": rename_handler, "CREATE_DIRECTORY": create_new_dir_handler, "UPLOAD": upload_file_handler, + "DOWNLOAD_FILE": download_file_handler, "SHARE": share_handler, } diff --git a/src/controllers/files/download_file/_download_file_test.py b/src/controllers/files/download_file/_download_file_test.py new file mode 100644 index 0000000..63ee71e --- /dev/null +++ b/src/controllers/files/download_file/_download_file_test.py @@ -0,0 +1,87 @@ +from random import randbytes +from main import app +from uuid import uuid4 +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +# DOWNLOAD FILE TESTS +download_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "uuid": None, + "name": "picture.jpeg", + "content": randbytes(1024), + }, +} + + +def test_download_bad_request(): + # Register an user + register_response = soap_client.service.account_register( + { + "username": download_test_data["username"], + "password": download_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": download_test_data["username"], + "password": download_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Unexistent file UUID + response = app.test_client().get( + f"/file/download/{uuid4()}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 404 + + # NO TOKEN + response = app.test_client().get( + f"/file/download/{download_test_data['file']['uuid']}" + ) + assert response.status_code == 401 + + +def test_download_success(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": download_test_data["username"], + "password": download_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file + create_file_response = soap_client.service.file_upload( + { + "token": token, + "fileName": download_test_data["file"]["name"], + "fileContent": download_test_data["file"]["content"], + "location": None, + } + ) + assert create_file_response.error is False + + # DOWNLOAD the file + file_response = app.test_client().get( + f"/file/download/{create_file_response.fileUUID}", + headers={"Authorization": f"Bearer {token}"}, + ) + file = file_response.data + + assert file_response.status_code == 200 + assert ( + file_response.headers["Content-Disposition"] + == f"attachment; filename={download_test_data['file']['name']}" + ) + assert len(file) == len(download_test_data["file"]["content"]) diff --git a/src/controllers/files/download_file/_handler.py b/src/controllers/files/download_file/_handler.py new file mode 100644 index 0000000..21ca13d --- /dev/null +++ b/src/controllers/files/download_file/_handler.py @@ -0,0 +1,25 @@ +import io +from flask import send_file +from src.config.soap_client import soap_client + + +def download_file_handler(token, file_uuid): + try: + request_data = {"fileUUID": file_uuid, "token": token} + response = soap_client.service.file_download(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + response_file = send_file( + path_or_file=io.BytesIO(response["fileContent"]), + as_attachment=True, + mimetype="application/octet-stream", + download_name=response["fileName"], + ) + + return response_file + + except Exception as e: + print("[Exception] download_file_handler ->", e) + return {"msg": "There was an error downloading the file"}, 500 diff --git a/src/views/_file_views.py b/src/views/_file_views.py index 2d59c07..2efec26 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -30,6 +30,12 @@ def file_upload(token): return FILES_HANDLERS["UPLOAD"](token) +@views.route("/file/download/", methods=["GET"]) +@auth_middlewares.token_required +def file_download(token, file_uuid): + return FILES_HANDLERS["DOWNLOAD_FILE"](token, file_uuid) + + @views.route("/file/share", methods=["POST"]) @auth_middlewares.token_required def file_share(token): From 1e09c26c21e31ba95a1d7a4b482f1a1b5bc0c6d9 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sun, 15 Oct 2023 21:20:39 +0000 Subject: [PATCH 40/50] [ci skip] chore(release): v0.9.0 [skip ci] --- CHANGELOG.md | 19 ++++++++++--------- version.json | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b00ddb..1be8b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# [0.9.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.2...v0.9.0) (2023-10-15) + + +### Features + +* download file ([#60](https://github.com/hawks-atlanta/proxy-python/issues/60)) ([9bf0cdb](https://github.com/hawks-atlanta/proxy-python/commit/9bf0cdb8b7d4901fdcad1991c4a88766bf1a3bf2)) +* share a file with another user ([#61](https://github.com/hawks-atlanta/proxy-python/issues/61)) ([a6f7074](https://github.com/hawks-atlanta/proxy-python/commit/a6f7074c3e69edda8bc3c91c370b917c14cc3cc6)) + + + ## [0.8.2](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.1...v0.8.2) (2023-10-15) @@ -34,12 +44,3 @@ -# [0.6.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.5.0...v0.6.0) (2023-10-13) - - -### Features - -* Update password ([#51](https://github.com/hawks-atlanta/proxy-python/issues/51)) ([395b44b](https://github.com/hawks-atlanta/proxy-python/commit/395b44bf9223cf1126521f80ac0ae1796d2207de)) - - - diff --git a/version.json b/version.json index f3d94d1..d9f1c3f 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.8.2" + "version": "0.9.0" } \ No newline at end of file From 6daf51f7860e49bdd5d48c2878a525e14bb08c9a Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Sun, 15 Oct 2023 09:54:50 -1200 Subject: [PATCH 41/50] feat: list user files (#64) --- docs/spec.openapi.yaml | 12 +- .../account/register_user/_handler.py | 2 +- .../account/update_password/_handler.py | 4 +- .../authentication/login/_handler.py | 2 +- .../authentication/refresh/_handler.py | 2 +- src/controllers/files/__init__.py | 3 +- .../files/check_file_status/_handler.py | 2 +- .../files/create_directory/_handler.py | 2 +- src/controllers/files/list_files/_handler.py | 43 +++++ .../files/list_files/_list_files_test.py | 150 ++++++++++++++++++ src/controllers/files/upload_file/_handler.py | 4 +- src/views/_file_views.py | 6 + 12 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 src/controllers/files/list_files/_handler.py create mode 100644 src/controllers/files/list_files/_list_files_test.py diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 388f036..ff07e52 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -372,9 +372,15 @@ paths: content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/fileDetails" + type: object + properties: + files: + type: array + items: + $ref: "#/components/schemas/fileDetails" + msg: + type: string + example: "Files have been listed successfully" "401": description: Not authorized content: diff --git a/src/controllers/account/register_user/_handler.py b/src/controllers/account/register_user/_handler.py index fce158d..199909f 100644 --- a/src/controllers/account/register_user/_handler.py +++ b/src/controllers/account/register_user/_handler.py @@ -29,5 +29,5 @@ def register_handler(): return {"msg": "Invalid JSON data provided in the request"}, 400 except Exception as e: - print("[Exception] register_handler ->", str(e)) + print("[Exception] register_handler ->", e) return {"msg": "There was an error registering the user"}, 500 diff --git a/src/controllers/account/update_password/_handler.py b/src/controllers/account/update_password/_handler.py index 0a0d365..a5df733 100644 --- a/src/controllers/account/update_password/_handler.py +++ b/src/controllers/account/update_password/_handler.py @@ -25,5 +25,5 @@ def update_password_handler(token): return {"msg": "Invalid JSON data provided in the request"}, 400 except Exception as e: - print("[Exception] password_handler ->", str(e)) - return {"msg": "Internal error", "error": str(e)}, 500 + print("[Exception] password_handler ->", e) + return {"msg": "Internal error"}, 500 diff --git a/src/controllers/authentication/login/_handler.py b/src/controllers/authentication/login/_handler.py index 3644f07..0643e12 100644 --- a/src/controllers/authentication/login/_handler.py +++ b/src/controllers/authentication/login/_handler.py @@ -28,5 +28,5 @@ def login_handler(): return {"msg": "Invalid JSON data provided in the request"}, 400 except Exception as e: - print("[Exception] login_handler ->", str(e)) + print("[Exception] login_handler ->", e) return {"msg": "There was an error logging in"}, 500 diff --git a/src/controllers/authentication/refresh/_handler.py b/src/controllers/authentication/refresh/_handler.py index 9c962ec..0e4696e 100644 --- a/src/controllers/authentication/refresh/_handler.py +++ b/src/controllers/authentication/refresh/_handler.py @@ -14,5 +14,5 @@ def challenge_handler(token): return {"msg": response.msg}, response.code except Exception as e: - print("[Exception] challenge ->", str(e)) + print("[Exception] challenge ->", e) return {"msg": "There was an error validating or refreshing the session"}, 500 diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index e6705c1..39d824e 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -2,7 +2,7 @@ from .rename_file._handler import rename_handler from .create_directory._handler import create_new_dir_handler from .upload_file._handler import upload_file_handler - +from .list_files._handler import list_files_handler from .download_file._handler import download_file_handler from .share_file._handler import share_handler @@ -11,6 +11,7 @@ "RENAME": rename_handler, "CREATE_DIRECTORY": create_new_dir_handler, "UPLOAD": upload_file_handler, + "FILE_LIST": list_files_handler, "DOWNLOAD_FILE": download_file_handler, "SHARE": share_handler, } diff --git a/src/controllers/files/check_file_status/_handler.py b/src/controllers/files/check_file_status/_handler.py index 36cbc8d..4bbf9c6 100644 --- a/src/controllers/files/check_file_status/_handler.py +++ b/src/controllers/files/check_file_status/_handler.py @@ -21,5 +21,5 @@ def check_state_handler(token, file_uuid): "ready": response.ready, }, response.code except Exception as e: - print("[Exception] check_state_handler ->", str(e)) + print("[Exception] check_state_handler ->", e) return {"msg": "There was an error checking the file state"}, 500 diff --git a/src/controllers/files/create_directory/_handler.py b/src/controllers/files/create_directory/_handler.py index c791e61..8ceeaac 100644 --- a/src/controllers/files/create_directory/_handler.py +++ b/src/controllers/files/create_directory/_handler.py @@ -37,5 +37,5 @@ def create_new_dir_handler(token): return {"msg": "Invalid JSON data provided in the request"}, 400 except Exception as e: - print("[Exception] create_new_directory ->", str(e)) + print("[Exception] create_new_directory ->", e) return {"msg": "There was an error creating the new directory"}, 500 diff --git a/src/controllers/files/list_files/_handler.py b/src/controllers/files/list_files/_handler.py new file mode 100644 index 0000000..a8aaf13 --- /dev/null +++ b/src/controllers/files/list_files/_handler.py @@ -0,0 +1,43 @@ +import flask +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def list_files_handler(token): + try: + directory_uuid = flask.request.args.get("directoryUUID", None) + + # Validate directoryUUID + not_valid_directory_uuid = directory_uuid is not None and not is_valid_uuid( + directory_uuid + ) + + if not_valid_directory_uuid: + return {"msg": "Not valid directory UUID provided"}, 400 + + # Send the request + request_data = {"token": token, "location": directory_uuid} + response = soap_client.service.file_list(request_data) + + if response.error is True: + return {"error": response.msg}, response.code + + # Parse files into dict + files = [ + { + "name": file.name, + "extension": file.extension, + "isFile": file.isFile, + "uuid": file.uuid, + "size": file.size, + } + for file in response.files + ] + + return { + "files": files, + "msg": "Files have been listed successfully", + }, 200 + except Exception as e: + print("[Exception] list_files_handler ->", e) + return {"msg": "There was an error listing the files"}, 500 diff --git a/src/controllers/files/list_files/_list_files_test.py b/src/controllers/files/list_files/_list_files_test.py new file mode 100644 index 0000000..5385484 --- /dev/null +++ b/src/controllers/files/list_files/_list_files_test.py @@ -0,0 +1,150 @@ +import json +from random import randbytes +from main import app + +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +list_files_test_data = { + "owner_username": fake_username(), + "second_username": fake_username(), + "password": fake_password(), + "directory": {"uuid": None, "name": "directory"}, + "file": { + "uuid": None, + "name": "picture.jpeg", + "content": randbytes(1024), + }, + "nested_file": { + "uuid": None, + "name": "nested_picture.jpeg", + "content": randbytes(1024), + }, +} + + +def test_list_files_bad_request(): + # Register the users + register_response = soap_client.service.account_register( + { + "username": list_files_test_data["owner_username"], + "password": list_files_test_data["password"], + } + ) + assert register_response.error is False + + register_response = soap_client.service.account_register( + { + "username": list_files_test_data["second_username"], + "password": list_files_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the registered user + login_response = soap_client.service.auth_login( + { + "username": list_files_test_data["owner_username"], + "password": list_files_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Send a request with no token + response = app.test_client().get("/file/list") + assert response.status_code == 401 + + # Send a request with not valid directoryUUID + response = app.test_client().get( + "/file/list?directoryUUID=not_valid_uuid", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_list_files_success(): + # Login with the owner user + login_response = soap_client.service.auth_login( + { + "username": list_files_test_data["owner_username"], + "password": list_files_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload a file in the root directory + upload_response = soap_client.service.file_upload( + { + "fileName": list_files_test_data["file"]["name"], + "fileContent": list_files_test_data["file"]["content"], + "location": None, + "token": token, + } + ) + assert upload_response.error is False + list_files_test_data["file"]["uuid"] = upload_response.fileUUID + + # Create a directory in the root directory + create_dir_response = soap_client.service.file_new_dir( + { + "directoryName": list_files_test_data["directory"]["name"], + "location": None, + "token": token, + } + ) + assert create_dir_response.error is False + list_files_test_data["directory"]["uuid"] = create_dir_response.fileUUID + + # Upload a file in the created directory + upload_response = soap_client.service.file_upload( + { + "fileName": list_files_test_data["nested_file"]["name"], + "fileContent": list_files_test_data["nested_file"]["content"], + "location": list_files_test_data["directory"]["uuid"], + "token": token, + } + ) + assert upload_response.error is False + list_files_test_data["nested_file"]["uuid"] = upload_response.fileUUID + + # List the files in the root directory + response = app.test_client().get( + "/file/list", headers={"Authorization": f"Bearer {token}"} + ) + response_json = json.loads(response.data) + + assert response.status_code == 200 + assert response_json["msg"] == "Files have been listed successfully" + assert len(response_json["files"]) == 2 + + # List the files in the created directory + response = app.test_client().get( + f"/file/list?directoryUUID={list_files_test_data['directory']['uuid']}", + headers={"Authorization": f"Bearer {token}"}, + ) + response_json = json.loads(response.data) + + assert response.status_code == 200 + assert response_json["msg"] == "Files have been listed successfully" + assert len(response_json["files"]) == 1 + + +def test_list_files_forbidden(): + # Login with the second user + login_response = soap_client.service.auth_login( + { + "username": list_files_test_data["second_username"], + "password": list_files_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # List the files in the created directory + response = app.test_client().get( + f"/file/list?directoryUUID={list_files_test_data['directory']['uuid']}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 403 diff --git a/src/controllers/files/upload_file/_handler.py b/src/controllers/files/upload_file/_handler.py index 2ac618e..ef5da95 100644 --- a/src/controllers/files/upload_file/_handler.py +++ b/src/controllers/files/upload_file/_handler.py @@ -41,5 +41,5 @@ def upload_file_handler(token): return {"msg": "The file is being uploaded", "fileUUID": fileId}, 201 except Exception as e: - print("[Exception] file_download_handler ->", str(e)) - return {"msg": "Internal error", "error": str(e)}, 500 + print("[Exception] file_download_handler ->", e) + return {"msg": "Internal error"}, 500 diff --git a/src/views/_file_views.py b/src/views/_file_views.py index 2efec26..804c648 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -30,6 +30,12 @@ def file_upload(token): return FILES_HANDLERS["UPLOAD"](token) +@views.route("/file/list", methods=["GET"]) +@auth_middlewares.token_required +def file_list(token): + return FILES_HANDLERS["FILE_LIST"](token) + + @views.route("/file/download/", methods=["GET"]) @auth_middlewares.token_required def file_download(token, file_uuid): From 62fa02d136f980583034bdcba495c5c79f872cde Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sun, 15 Oct 2023 21:55:07 +0000 Subject: [PATCH 42/50] [ci skip] chore(release): v0.10.0 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be8b0a..2bd729e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.10.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.9.0...v0.10.0) (2023-10-15) + + +### Features + +* list user files ([#64](https://github.com/hawks-atlanta/proxy-python/issues/64)) ([6daf51f](https://github.com/hawks-atlanta/proxy-python/commit/6daf51f7860e49bdd5d48c2878a525e14bb08c9a)) + + + # [0.9.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.2...v0.9.0) (2023-10-15) @@ -35,12 +44,3 @@ -# [0.7.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.6.0...v0.7.0) (2023-10-14) - - -### Features - -* create a folder ([#52](https://github.com/hawks-atlanta/proxy-python/issues/52)) ([7aca7f2](https://github.com/hawks-atlanta/proxy-python/commit/7aca7f204ce10c37154bcf1ed716cd13ca02c142)) - - - diff --git a/version.json b/version.json index d9f1c3f..eab3c25 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.9.0" + "version": "0.10.0" } \ No newline at end of file From b2926217b2e4cc03ea59525e1f59c0f2167b6791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Andr=C3=A9s=20Chaparro=20Quintero?= <62714297+PedroChaparro@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:00:00 -0500 Subject: [PATCH 43/50] feat: Get file by UUID (#66) * docs(openapi): Update spec * feat(files): Add endpoint to get file by its UUID * test(files): Add tests to the new endpoint --- docs/spec.openapi.yaml | 31 +++++ src/controllers/files/__init__.py | 2 + .../_get_file_by_uuid_test.py | 131 ++++++++++++++++++ .../files/get_file_by_uuid/_handler.py | 30 ++++ src/views/_file_views.py | 6 + 5 files changed, 200 insertions(+) create mode 100644 src/controllers/files/get_file_by_uuid/_get_file_by_uuid_test.py create mode 100644 src/controllers/files/get_file_by_uuid/_handler.py diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index ff07e52..76e6b11 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -319,6 +319,34 @@ paths: $ref: "#/components/schemas/statusResponse" /file/{fileUUID}: + get: + tags: + - Files + security: + - bearer: [] + description: Get a file by its UUID + parameters: + - in: path + name: fileUUID + required: true + schema: + type: string + description: The ID of the file to get + responses: + "200": + description: File successfully obtained + content: + aplication/json: + schema: + type: object + properties: + file: + type: object + allOf: + - $ref: "#/components/schemas/fileDetails" + msg: + type: string + example: "The file have been obtained successfully" delete: tags: - Files @@ -663,6 +691,9 @@ components: name: type: string example: "pixture.png" + extension: + type: string + example: "image/png" size: type: integer example: 4687 diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index 39d824e..2e94621 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -2,6 +2,7 @@ from .rename_file._handler import rename_handler from .create_directory._handler import create_new_dir_handler from .upload_file._handler import upload_file_handler +from .get_file_by_uuid._handler import get_file_handler from .list_files._handler import list_files_handler from .download_file._handler import download_file_handler from .share_file._handler import share_handler @@ -11,6 +12,7 @@ "RENAME": rename_handler, "CREATE_DIRECTORY": create_new_dir_handler, "UPLOAD": upload_file_handler, + "GET_BY_UUID": get_file_handler, "FILE_LIST": list_files_handler, "DOWNLOAD_FILE": download_file_handler, "SHARE": share_handler, diff --git a/src/controllers/files/get_file_by_uuid/_get_file_by_uuid_test.py b/src/controllers/files/get_file_by_uuid/_get_file_by_uuid_test.py new file mode 100644 index 0000000..6a83f6b --- /dev/null +++ b/src/controllers/files/get_file_by_uuid/_get_file_by_uuid_test.py @@ -0,0 +1,131 @@ +import json +from random import randbytes +from uuid import uuid4 + +from main import app +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +get_file_test_data = { + "username": fake_username(), + "password": fake_password(), + "directory": {"uuid": None, "name": "directory"}, + "file": { + "uuid": None, + "name": "picture.jpeg", + "content": randbytes(1024), + }, +} + + +def test_get_file_by_uuid_bad_request(): + # Register test user + register_response = soap_client.service.account_register( + { + "username": get_file_test_data["username"], + "password": get_file_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_file_test_data["username"], + "password": get_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Not valid file UUID (400 Bad Request) + response = app.test_client().get( + "/file/1234", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + +def test_get_file_by_uuid_success(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_file_test_data["username"], + "password": get_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Upload file + upload_response = soap_client.service.file_upload( + { + "token": token, + "directoryUUID": None, + "fileName": get_file_test_data["file"]["name"], + "fileContent": get_file_test_data["file"]["content"], + } + ) + assert upload_response.error is False + get_file_test_data["file"]["uuid"] = upload_response.fileUUID + + # Create directory + create_directory_response = soap_client.service.file_new_dir( + { + "token": token, + "directoryName": get_file_test_data["directory"]["name"], + } + ) + assert create_directory_response.error is False + get_file_test_data["directory"]["uuid"] = create_directory_response.fileUUID + + # Get file + response = app.test_client().get( + f"/file/{get_file_test_data['file']['uuid']}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + + # Check response + response_json = json.loads(response.data) + assert response_json["msg"] == "The file have been obtained successfully" + assert response_json["file"]["uuid"] == get_file_test_data["file"]["uuid"] + assert response_json["file"]["name"] == get_file_test_data["file"]["name"] + assert response_json["file"]["extension"] is None + assert response_json["file"]["size"] == len(get_file_test_data["file"]["content"]) + assert response_json["file"]["isFile"] is True + + # Get directory + response = app.test_client().get( + f"/file/{get_file_test_data['directory']['uuid']}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 200 + + # Check response + response_json = json.loads(response.data) + assert response_json["msg"] == "The file have been obtained successfully" + assert response_json["file"]["uuid"] == get_file_test_data["directory"]["uuid"] + assert response_json["file"]["name"] == get_file_test_data["directory"]["name"] + assert response_json["file"]["extension"] is None + assert response_json["file"]["size"] == 0 + assert response_json["file"]["isFile"] is False + + +def test_get_file_by_uuid_not_found(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": get_file_test_data["username"], + "password": get_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Get file + response = app.test_client().get( + f"/file/{uuid4()}", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 404 diff --git a/src/controllers/files/get_file_by_uuid/_handler.py b/src/controllers/files/get_file_by_uuid/_handler.py new file mode 100644 index 0000000..cb157de --- /dev/null +++ b/src/controllers/files/get_file_by_uuid/_handler.py @@ -0,0 +1,30 @@ +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def get_file_handler(token, file_uuid): + try: + # Check if the file uuid is valid + not_valid_file_uuid = not file_uuid or not is_valid_uuid(file_uuid) + if not_valid_file_uuid: + return {"msg": "Not valid file uuid provided"}, 400 + + # Send the request to the SOAP service + response = soap_client.service.file_get({"token": token, "fileUUID": file_uuid}) + if response.error is True: + return {"msg": response.msg}, response.code + + response_file = response.file + return { + "msg": "The file have been obtained successfully", + "file": { + "uuid": response_file.uuid, + "name": response_file.name, + "extension": response_file.extension, + "size": response_file.size, + "isFile": response_file.isFile, + }, + }, 200 + except Exception as e: + print("[Exception] get_file_handler ->", str(e)) + return {"msg": "There was an error while obtaining the file"}, 500 diff --git a/src/views/_file_views.py b/src/views/_file_views.py index 804c648..de8bb99 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -30,6 +30,12 @@ def file_upload(token): return FILES_HANDLERS["UPLOAD"](token) +@views.route("/file/", methods=["GET"]) +@auth_middlewares.token_required +def file_get(token, file_uuid): + return FILES_HANDLERS["GET_BY_UUID"](token, file_uuid) + + @views.route("/file/list", methods=["GET"]) @auth_middlewares.token_required def file_list(token): From d999d867a4ff7d6bfd7ec4f89cd59d52cd3d8932 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Sun, 15 Oct 2023 22:00:16 +0000 Subject: [PATCH 44/50] [ci skip] chore(release): v0.11.0 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bd729e..4d0d2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.11.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.10.0...v0.11.0) (2023-10-15) + + +### Features + +* Get file by UUID ([#66](https://github.com/hawks-atlanta/proxy-python/issues/66)) ([b292621](https://github.com/hawks-atlanta/proxy-python/commit/b2926217b2e4cc03ea59525e1f59c0f2167b6791)) + + + # [0.10.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.9.0...v0.10.0) (2023-10-15) @@ -35,12 +44,3 @@ -# [0.8.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.7.0...v0.8.0) (2023-10-14) - - -### Features - -* file upload ([#53](https://github.com/hawks-atlanta/proxy-python/issues/53)) ([097d026](https://github.com/hawks-atlanta/proxy-python/commit/097d0267b4596ee8cea5f24e4b4ceaf019ff801d)) - - - diff --git a/version.json b/version.json index eab3c25..2cd0808 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.10.0" + "version": "0.11.0" } \ No newline at end of file From b0a585c89c8ae09a49fa0549858d73f51ea6976a Mon Sep 17 00:00:00 2001 From: Miguel Mateo Mendoza Rojas <115849391+MiguelMRojas@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:44:56 -1200 Subject: [PATCH 45/50] feat: move a file (#68) --- src/controllers/files/__init__.py | 2 + src/controllers/files/move_a_file/_handler.py | 36 +++++ .../files/move_a_file/_move_file_test.py | 133 ++++++++++++++++++ src/views/_file_views.py | 6 + 4 files changed, 177 insertions(+) create mode 100644 src/controllers/files/move_a_file/_handler.py create mode 100644 src/controllers/files/move_a_file/_move_file_test.py diff --git a/src/controllers/files/__init__.py b/src/controllers/files/__init__.py index 2e94621..70f4c83 100644 --- a/src/controllers/files/__init__.py +++ b/src/controllers/files/__init__.py @@ -6,6 +6,7 @@ from .list_files._handler import list_files_handler from .download_file._handler import download_file_handler from .share_file._handler import share_handler +from .move_a_file._handler import file_move_handler FILES_HANDLERS = { "CHECK_STATE": check_state_handler, @@ -16,4 +17,5 @@ "FILE_LIST": list_files_handler, "DOWNLOAD_FILE": download_file_handler, "SHARE": share_handler, + "MOVE_FILE": file_move_handler, } diff --git a/src/controllers/files/move_a_file/_handler.py b/src/controllers/files/move_a_file/_handler.py new file mode 100644 index 0000000..39bd376 --- /dev/null +++ b/src/controllers/files/move_a_file/_handler.py @@ -0,0 +1,36 @@ +import json +from flask import request +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def file_move_handler(token, file_uuid): + try: + data = json.loads(request.data) + target_directory_uuid = data.get("targetDirectoryUUID") + + not_valid_target_directory = not target_directory_uuid or not is_valid_uuid( + target_directory_uuid + ) + if not_valid_target_directory: + return {"msg": "The target directory is not valid or was not provided"}, 400 + + request_data = { + "token": token, + "fileUUID": file_uuid, + "targetDirectoryUUID": target_directory_uuid, + } + + response = soap_client.service.file_move(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return {"msg": "The file has been moved"}, 200 + + except ValueError: + return {"msg": "Not valid JSON data provided in the request"}, 400 + + except Exception as e: + print("[Exception] file_move_handler ->", e) + return {"msg": "There was an error moving the file"}, 500 diff --git a/src/controllers/files/move_a_file/_move_file_test.py b/src/controllers/files/move_a_file/_move_file_test.py new file mode 100644 index 0000000..b8ad768 --- /dev/null +++ b/src/controllers/files/move_a_file/_move_file_test.py @@ -0,0 +1,133 @@ +import json +from random import randbytes +from uuid import uuid4 + +from main import app +from src.config.soap_client import soap_client +from src.lib.faker import fake_username, fake_password + +# MOVE FILE TESTS +move_file_test_data = { + "username": fake_username(), + "password": fake_password(), + "file": { + "uuid": None, + "targetDirectoryUUID": None, + "name": "test.txt", + "content": randbytes(1024), + }, +} + + +def test_move_file_success(): + # Register a user + register_response = soap_client.service.account_register( + { + "username": move_file_test_data["username"], + "password": move_file_test_data["password"], + } + ) + assert register_response.error is False + + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": move_file_test_data["username"], + "password": move_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Create a new directory + new_directory_name = "test_directory" + create_dir_response = soap_client.service.file_new_dir( + { + "directoryName": new_directory_name, + "location": None, + "token": token, + } + ) + assert create_dir_response["code"] == 201 + directory_uuid = create_dir_response["fileUUID"] + move_file_test_data["file"]["targetDirectoryUUID"] = directory_uuid + + # Upload a file + create_file_response = soap_client.service.file_upload( + { + "token": token, + "fileName": move_file_test_data["file"]["name"], + "fileContent": move_file_test_data["file"]["content"], + "location": None, + } + ) + assert create_file_response.error is False + move_file_test_data["file"]["uuid"] = create_file_response.fileUUID + + # Move the file to the target directory + response = app.test_client().patch( + f"/file/{move_file_test_data['file']['uuid']}/move", + json={"targetDirectoryUUID": directory_uuid}, + headers={"Authorization": f"Bearer {token}"}, + ) + + json_response = json.loads(response.data) + assert response.status_code == 200 + assert json_response["msg"].startswith("The file has been moved") + + +def test_move_file_bad_request(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": move_file_test_data["username"], + "password": move_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Missing JSON data + response = app.test_client().patch( + f"/file/{move_file_test_data['file']['uuid']}/move", + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + # Not valid target directory UUID + response = app.test_client().patch( + f"/file/{move_file_test_data['file']['uuid']}/move", + json={"targetDirectoryUUID": "not_valid_uuid"}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 400 + + # Non existent file UUID + response = app.test_client().patch( + f"/file/{uuid4()}/move", + json={"targetDirectoryUUID": uuid4()}, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 404 + + +def test_move_file_conflict(): + # Login with the user + login_response = soap_client.service.auth_login( + { + "username": move_file_test_data["username"], + "password": move_file_test_data["password"], + } + ) + assert login_response.error is False + token = login_response.auth.token + + # Try to move the file to the same directory + response = app.test_client().patch( + f"/file/{move_file_test_data['file']['uuid']}/move", + json={ + "targetDirectoryUUID": move_file_test_data["file"]["targetDirectoryUUID"] + }, + headers={"Authorization": f"Bearer {token}"}, + ) + assert response.status_code == 409 diff --git a/src/views/_file_views.py b/src/views/_file_views.py index de8bb99..8adbd56 100644 --- a/src/views/_file_views.py +++ b/src/views/_file_views.py @@ -52,3 +52,9 @@ def file_download(token, file_uuid): @auth_middlewares.token_required def file_share(token): return FILES_HANDLERS["SHARE"](token) + + +@views.route("/file//move", methods=["PATCH"]) +@auth_middlewares.token_required +def move_file(token, file_uuid): + return FILES_HANDLERS["MOVE_FILE"](token, file_uuid) From 079d7555eacec86703035b57ea4edf387aaa231d Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:55:58 -0500 Subject: [PATCH 46/50] refactor: Create delete file controller (#69) * feat: delete file endpoint Endpoint to delete files, no tests implemented yet * fix: check format check format error * fix: Removing changes to init.py in controllers files_view.py files * fix: check-linter problem --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> --- src/controllers/files/remove_file/_handler.py | 16 ++++++++++++++++ .../files/remove_file/_remove_file_test.py | 0 2 files changed, 16 insertions(+) create mode 100644 src/controllers/files/remove_file/_handler.py create mode 100644 src/controllers/files/remove_file/_remove_file_test.py diff --git a/src/controllers/files/remove_file/_handler.py b/src/controllers/files/remove_file/_handler.py new file mode 100644 index 0000000..a404276 --- /dev/null +++ b/src/controllers/files/remove_file/_handler.py @@ -0,0 +1,16 @@ +from src.config.soap_client import soap_client + + +def remove_file_handler(token, file_uuid): + try: + request_data = {"fileUUID": file_uuid, "token": token} + response = soap_client.service.file_delete(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return {"msg": "File successfully deleted"}, 200 + + except Exception as e: + print("[Exception] remove_file_handler ->", e) + return {"msg": "There was an error deleting the file"}, 500 diff --git a/src/controllers/files/remove_file/_remove_file_test.py b/src/controllers/files/remove_file/_remove_file_test.py new file mode 100644 index 0000000..e69de29 From eb7012410fdeb7bb943fb2fdee0c60fc88870167 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:10:00 -0500 Subject: [PATCH 47/50] refactor: Unshare file (#70) Endpoint to unshare file using token, other user's name, and file id. Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> --- .../files/unshare_file/_handler.py | 39 +++++++++++++++++++ .../files/unshare_file/_unshare_file_test.py | 0 2 files changed, 39 insertions(+) create mode 100644 src/controllers/files/unshare_file/_handler.py create mode 100644 src/controllers/files/unshare_file/_unshare_file_test.py diff --git a/src/controllers/files/unshare_file/_handler.py b/src/controllers/files/unshare_file/_handler.py new file mode 100644 index 0000000..55b225b --- /dev/null +++ b/src/controllers/files/unshare_file/_handler.py @@ -0,0 +1,39 @@ +import json +from flask import request +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def unshare_handler(token): + try: + data = json.loads(request.data) + fileUUID = data["fileUUID"] + otherUsername = data["otherUsername"] + + # Check if required fields are empty + empty_file_uuid = not fileUUID or len(fileUUID) == 0 + empty_other_username = not otherUsername or len(otherUsername) == 0 + if empty_file_uuid or empty_other_username: + return {"msg": "Required fields are missing in form data"}, 400 + + # Check if file UUID is valid + if not is_valid_uuid(fileUUID): + return {"msg": "Not valid file UUID provided"}, 400 + + request_data = { + "fileUUID": fileUUID, + "otherUsername": otherUsername, + "token": token, + } + response = soap_client.service.unshare_file(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + else: + return {"msg": "File unshared successfully"}, 200 + + except ValueError: + return {"msg": "Invalid JSON data provided in the request"}, 400 + + except Exception: + return {"msg": "There was an error unsharing the file"}, 500 diff --git a/src/controllers/files/unshare_file/_unshare_file_test.py b/src/controllers/files/unshare_file/_unshare_file_test.py new file mode 100644 index 0000000..e69de29 From a30478bc6848d0a7dc1e85a8fd0b2a56333937d3 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:29:20 -0500 Subject: [PATCH 48/50] refactor: Shared files (#71) * feat: Endpoint to obtain the list of shares, the tests are not yet implemented Receives the user's token and returns the list of shared files/folders and the user they were shared with. * feat: Shared list endpoint Receives the user's token and returns the list of shared files/folders that have been shared with them * fix: check-format * refactor: Added the missing "extension" field Added the missing "extension" field in addition to changing sharedFile to files --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> --- src/controllers/files/shared_file/_handler.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/controllers/files/shared_file/_handler.py diff --git a/src/controllers/files/shared_file/_handler.py b/src/controllers/files/shared_file/_handler.py new file mode 100644 index 0000000..c22716f --- /dev/null +++ b/src/controllers/files/shared_file/_handler.py @@ -0,0 +1,30 @@ +from src.config.soap_client import soap_client + + +def shared_files_handler(token): + try: + request_data = {"token": token} + response = soap_client.service.share_list(request_data) + + if response["error"] is True: + return {"msg": response["msg"]}, response["code"] + + files = [ + { + "name": file.name, + "size": file.size, + "isFile": file.isFile, + "extension": file.extension, + "uuid": file.uuid, + "ownerusername": file.ownerusername, + } + for file in response.files + ] + + return { + "files": files, + "msg": "List of shared files obtained", + }, 200 + except Exception as e: + print("[Exception] shared_files_handler ->", e) + return {"msg": "There was an error listing the shared files"}, 500 From 9373052af503cdef5312c37f954ba163de17a5f9 Mon Sep 17 00:00:00 2001 From: Andrea Velasquez <55466005+andre154@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:30:02 -0500 Subject: [PATCH 49/50] refactor: Get the users with whom a file is being shared (#72) * feat: Get the users with whom a file is being shared Endpoint to get the users with whom a file is shared. Testing has not been implemented yet * refactor: Enpoint correction in addition to updating the spec --------- Co-authored-by: Andvelavi <54145562+Andvelavi@users.noreply.github.com> --- docs/spec.openapi.yaml | 2 +- .../files/shared_with_who/_handler.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/controllers/files/shared_with_who/_handler.py diff --git a/docs/spec.openapi.yaml b/docs/spec.openapi.yaml index 76e6b11..7514d5b 100644 --- a/docs/spec.openapi.yaml +++ b/docs/spec.openapi.yaml @@ -495,7 +495,7 @@ paths: schema: $ref: "#/components/schemas/statusResponse" - /file/shared-with-who: + /file/{fileUUID}/shared-with-who: get: tags: - Files diff --git a/src/controllers/files/shared_with_who/_handler.py b/src/controllers/files/shared_with_who/_handler.py new file mode 100644 index 0000000..f4780ea --- /dev/null +++ b/src/controllers/files/shared_with_who/_handler.py @@ -0,0 +1,29 @@ +from src.config.soap_client import soap_client +from src.lib.helpers import is_valid_uuid + + +def shared_with_who_handler(token, file_uuid): + try: + if not is_valid_uuid(file_uuid): + return {"msg": "Not valid file UUID provided"}, 400 + + request_data = {"fileUUID": file_uuid, "token": token} + response = soap_client.service.share_list_with_who(request_data) + + if response.usernames is None: + return {"msg": response["msg"]}, response["code"] + + usernames = [ + { + "users": usernames, + } + for usernames in response.usernames + ] + + return { + "users": usernames, + "msg": "List of users the file is shared with", + }, 200 + except Exception as e: + print("[Exception] shared_with_who_handler ->", e) + return {"msg": "There was an error listing the shared with"}, 500 From 3a285ce2d05f6acc1ad3054a9c194470c9a42f58 Mon Sep 17 00:00:00 2001 From: Antonio Donis Date: Fri, 20 Oct 2023 02:06:32 +0000 Subject: [PATCH 50/50] [ci skip] chore(release): v0.12.0 [skip ci] --- CHANGELOG.md | 18 +++++++++--------- version.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d0d2f2..ddf1317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [0.12.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.11.0...v0.12.0) (2023-10-20) + + +### Features + +* move a file ([#68](https://github.com/hawks-atlanta/proxy-python/issues/68)) ([b0a585c](https://github.com/hawks-atlanta/proxy-python/commit/b0a585c89c8ae09a49fa0549858d73f51ea6976a)) + + + # [0.11.0](https://github.com/hawks-atlanta/proxy-python/compare/v0.10.0...v0.11.0) (2023-10-15) @@ -35,12 +44,3 @@ -## [0.8.1](https://github.com/hawks-atlanta/proxy-python/compare/v0.8.0...v0.8.1) (2023-10-14) - - -### Bug Fixes - -* **files:** Keep files extension ([#56](https://github.com/hawks-atlanta/proxy-python/issues/56)) ([624cc06](https://github.com/hawks-atlanta/proxy-python/commit/624cc06af6443602bc9b946c8fc7ee5376dbea2c)) - - - diff --git a/version.json b/version.json index 2cd0808..239eb2a 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "0.11.0" + "version": "0.12.0" } \ No newline at end of file