From 336e3087c24d267414d6fb0f26710b259bcb2e61 Mon Sep 17 00:00:00 2001 From: Mayr Martin <22145802+mayrmartin@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:01:17 +0100 Subject: [PATCH 1/8] api: - add trace logging to api - add path aliases for lib import --- api/Dockerfile | 1 - api/README.md | 2 +- api/package-lock.json | 3208 ++++------------- api/package.json | 19 +- api/src/config.ts | 3 - api/src/global_permission_grant.ts | 2 + api/src/global_permission_revoke.ts | 2 + api/src/global_permissions_grant_all.ts | 2 + api/src/global_permissions_list.ts | 1 + api/src/group_create.ts | 5 + api/src/group_list.ts | 1 + api/src/group_member_add.ts | 2 + api/src/group_member_remove.ts | 2 + api/src/group_permissions_list.ts | 5 +- api/src/httpd/router.ts | 20 +- api/src/httpd/server.ts | 6 +- api/src/index.ts | 1 + api/src/lib/validation.ts | 25 +- .../approveNewNodeForExistingOrganization.ts | 7 +- .../controller/approveNewOrganization.ts | 8 +- api/src/network/controller/declineNode.ts | 4 +- api/src/network/controller/list.ts | 4 +- api/src/network/controller/listActive.ts | 4 +- api/src/network/controller/logNodes.ts | 2 +- api/src/network/controller/registerNode.ts | 4 +- api/src/network/controller/vote.ts | 9 +- api/src/network/model/Nodes.ts | 2 +- api/src/notification_count.ts | 1 + api/src/notification_list.ts | 13 +- api/src/notification_mark_read.ts | 2 + api/src/organization/organization.ts | 8 + api/src/project_assign.ts | 2 + api/src/project_budget_delete_projected.ts | 2 + api/src/project_budget_update_projected.ts | 2 + api/src/project_close.ts | 2 + api/src/project_create.ts | 2 + api/src/project_list.ts | 2 + api/src/project_permission_grant.ts | 2 + api/src/project_permission_revoke.ts | 2 + api/src/project_permissions_list.ts | 7 +- api/src/project_update.ts | 2 + api/src/project_view_details.ts | 7 +- api/src/project_view_history.ts | 20 +- api/src/project_view_history_v2.ts | 59 +- api/src/provisioning_end.ts | 2 + api/src/provisioning_get.ts | 1 + api/src/provisioning_start.ts | 2 + api/src/service/Client.ts | 3 + api/src/service/Client_storage_service.ts | 7 + api/src/service/ProjectEvents.ts | 65 - api/src/service/RpcClient.ts | 3 +- api/src/service/SubprojectEvents.ts | 167 - api/src/service/User.ts | 8 - api/src/service/Workflowitem.ts | 191 - api/src/service/cache.ts | 312 +- api/src/service/cache2.ts | 36 +- api/src/service/document_share.ts | 1 - api/src/service/document_upload.ts | 2 + api/src/service/document_validation.ts | 3 + .../service/domain/document/decryptprivkey.js | 5 + api/src/service/domain/document/document.ts | 2 + .../domain/document/document_download.spec.ts | 2 +- .../document/document_eventsourcing.spec.ts | 2 +- .../domain/document/document_eventsourcing.ts | 11 +- .../domain/document/document_get.spec.ts | 2 +- .../service/domain/document/document_get.ts | 17 +- .../domain/document/document_share.spec.ts | 2 +- .../service/domain/document/document_share.ts | 61 +- .../domain/document/document_shared.ts | 2 + .../domain/document/document_upload.spec.ts | 2 +- .../domain/document/document_upload.ts | 21 +- .../domain/document/document_uploaded.ts | 5 +- .../domain/document/document_validate.spec.ts | 2 +- .../domain/document/document_validate.ts | 71 +- .../service/domain/document/secret.spec.ts | 117 +- api/src/service/domain/document/secret_get.ts | 4 +- .../storage_service_url_eventsourcing.ts | 2 + .../document/storage_service_url_get.spec.ts | 52 +- .../document/storage_service_url_get.ts | 7 + .../storage_service_url_update.spec.ts | 23 +- .../document/storage_service_url_update.ts | 4 +- .../workflowitem_document_download.ts | 20 +- .../workflowitem_document_uploaded.ts | 2 +- .../service/domain/errors/already_exists.ts | 2 +- .../domain/errors/event_sourcing_error.ts | 2 +- .../service/domain/errors/invalid_command.ts | 6 +- .../service/domain/errors/invalid_event.ts | 6 +- .../service/domain/errors/not_authorized.ts | 2 +- api/src/service/domain/errors/not_found.ts | 2 +- .../domain/errors/precondition_error.ts | 2 +- .../service/domain/network/node_declined.ts | 4 +- .../service/domain/network/node_registered.ts | 14 +- .../service/domain/network/nodes_logged.ts | 3 + .../service/domain/organization/auth_token.ts | 10 +- .../domain/organization/group_create.spec.ts | 2 +- .../domain/organization/group_create.ts | 14 +- .../domain/organization/group_created.ts | 16 +- .../organization/group_eventsourcing.ts | 19 +- .../service/domain/organization/group_get.ts | 8 +- .../organization/group_member_add.spec.ts | 2 +- .../domain/organization/group_member_add.ts | 15 +- .../domain/organization/group_member_added.ts | 14 +- .../organization/group_member_remove.spec.ts | 2 +- .../organization/group_member_remove.ts | 12 +- .../organization/group_member_removed.ts | 12 +- .../organization/group_permissions_granted.ts | 12 +- .../organization/group_permissions_revoked.ts | 12 +- .../organization/public_key_eventsourcing.ts | 5 +- .../organization/public_key_get.spec.ts | 2 +- .../domain/organization/public_key_get.ts | 11 +- .../organization/public_key_publish.spec.ts | 2 +- .../domain/organization/public_key_publish.ts | 7 +- .../organization/public_key_published.ts | 4 +- .../organization/public_key_update.spec.ts | 2 +- .../domain/organization/public_key_update.ts | 7 +- .../domain/organization/public_key_updated.ts | 4 +- .../domain/organization/service_user.ts | 4 - .../domain/organization/user_create.spec.ts | 2 +- .../domain/organization/user_create.ts | 8 +- .../domain/organization/user_created.ts | 16 +- .../domain/organization/user_disable.spec.ts | 2 +- .../domain/organization/user_disable.ts | 5 +- .../domain/organization/user_disabled.ts | 4 +- .../domain/organization/user_enable.spec.ts | 2 +- .../domain/organization/user_enable.ts | 6 +- .../domain/organization/user_enabled.ts | 4 +- .../domain/organization/user_eventsourcing.ts | 13 +- .../service/domain/organization/user_get.ts | 9 +- .../organization/user_password_change.spec.ts | 2 +- .../organization/user_password_change.ts | 17 +- .../organization/user_password_changed.ts | 4 +- .../user_permission_grant.spec.ts | 2 +- .../organization/user_permission_grant.ts | 12 +- .../organization/user_permission_granted.ts | 4 +- .../user_permission_revoke.spec.ts | 2 +- .../organization/user_permission_revoke.ts | 11 +- .../organization/user_permission_revoked.ts | 4 +- .../system_information/provisioning_end.ts | 8 +- .../system_information/provisioning_ended.ts | 2 + .../system_information/provisioning_get.ts | 9 +- .../system_information/provisioning_start.ts | 7 +- .../provisioning_started.ts | 3 + .../system_information_eventsourcing.ts | 4 +- .../workflow/global_permission_grant.ts | 43 +- .../workflow/global_permission_granted.ts | 10 +- .../workflow/global_permission_revoke.ts | 44 +- .../workflow/global_permission_revoked.ts | 14 +- .../domain/workflow/global_permissions.ts | 8 +- .../global_permissions_eventsourcing.ts | 9 +- .../domain/workflow/global_permissions_get.ts | 10 +- .../workflow/global_permissions_grant.spec.ts | 2 +- .../global_permissions_revoke.spec.ts | 2 +- .../domain/workflow/notification_created.ts | 10 +- .../workflow/notification_eventsourcing.ts | 10 +- .../domain/workflow/notification_list.ts | 2 +- .../domain/workflow/notification_mark_read.ts | 9 +- .../workflow/notification_marked_read.ts | 11 +- .../domain/workflow/project_assign.spec.ts | 2 +- .../service/domain/workflow/project_assign.ts | 37 +- .../domain/workflow/project_assigned.ts | 1 - .../domain/workflow/project_close.spec.ts | 2 +- .../service/domain/workflow/project_close.ts | 10 +- .../service/domain/workflow/project_closed.ts | 1 - .../domain/workflow/project_create.spec.ts | 2 +- .../service/domain/workflow/project_create.ts | 10 +- .../domain/workflow/project_created.ts | 6 +- .../domain/workflow/project_eventsourcing.ts | 6 +- .../domain/workflow/project_get.spec.ts | 2 +- .../service/domain/workflow/project_get.ts | 37 +- .../workflow/project_history_get.spec.ts | 2 +- .../domain/workflow/project_history_get.ts | 11 +- .../domain/workflow/project_list.spec.ts | 2 +- .../service/domain/workflow/project_list.ts | 40 +- .../workflow/project_permission_grant.spec.ts | 2 +- .../workflow/project_permission_grant.ts | 5 +- .../workflow/project_permission_granted.ts | 4 +- .../project_permission_revoke.spec.ts | 2 +- .../workflow/project_permission_revoke.ts | 19 +- .../workflow/project_permission_revoked.ts | 5 +- .../workflow/project_permissions_list.spec.ts | 2 +- .../workflow/project_permissions_list.ts | 5 +- .../project_projected_budget_delete.spec.ts | 2 +- .../project_projected_budget_delete.ts | 54 +- .../project_projected_budget_deleted.ts | 1 - .../project_projected_budget_update.spec.ts | 2 +- .../project_projected_budget_update.ts | 75 +- .../project_projected_budget_updated.ts | 1 - .../domain/workflow/project_update.spec.ts | 2 +- .../service/domain/workflow/project_update.ts | 14 +- .../domain/workflow/project_updated.ts | 5 +- .../domain/workflow/subproject_assign.spec.ts | 2 +- .../domain/workflow/subproject_assign.ts | 29 +- .../domain/workflow/subproject_assigned.ts | 1 - .../domain/workflow/subproject_close.spec.ts | 2 +- .../domain/workflow/subproject_close.ts | 16 +- .../domain/workflow/subproject_closed.ts | 1 - .../domain/workflow/subproject_create.spec.ts | 2 +- .../domain/workflow/subproject_create.ts | 13 +- .../domain/workflow/subproject_created.ts | 6 +- .../workflow/subproject_eventsourcing.ts | 6 +- .../domain/workflow/subproject_get.spec.ts | 2 +- .../service/domain/workflow/subproject_get.ts | 4 +- .../workflow/subproject_history_get.spec.ts | 2 +- .../domain/workflow/subproject_history_get.ts | 12 +- .../domain/workflow/subproject_list.spec.ts | 2 +- .../domain/workflow/subproject_list.ts | 4 +- .../subproject_permission_grant.spec.ts | 2 +- .../workflow/subproject_permission_grant.ts | 17 +- .../workflow/subproject_permission_granted.ts | 1 - .../subproject_permission_revoke.spec.ts | 2 +- .../workflow/subproject_permission_revoke.ts | 25 +- .../workflow/subproject_permission_revoked.ts | 1 - .../subproject_permissions_list.spec.ts | 2 +- .../workflow/subproject_permissions_list.ts | 6 +- ...subproject_projected_budget_delete.spec.ts | 2 +- .../subproject_projected_budget_delete.ts | 76 +- ...subproject_projected_budget_update.spec.ts | 2 +- .../subproject_projected_budget_update.ts | 76 +- .../domain/workflow/subproject_update.spec.ts | 2 +- .../domain/workflow/subproject_update.ts | 13 +- .../workflow/user_assignment_get.spec.ts | 2 +- .../domain/workflow/user_assignments_get.ts | 14 +- .../workflow/workflowitem_assign.spec.ts | 2 +- .../domain/workflow/workflowitem_assign.ts | 13 +- .../workflow/workflowitem_close.spec.ts | 2 +- .../domain/workflow/workflowitem_close.ts | 15 +- .../workflow/workflowitem_create.spec.ts | 2 +- .../domain/workflow/workflowitem_create.ts | 47 +- .../domain/workflow/workflowitem_created.ts | 2 +- .../workflow/workflowitem_eventsourcing.ts | 8 +- .../domain/workflow/workflowitem_get.spec.ts | 2 +- .../domain/workflow/workflowitem_get.ts | 5 +- .../workflow/workflowitem_get_details.spec.ts | 2 +- .../workflow/workflowitem_get_details.ts | 4 +- .../workflow/workflowitem_history_get.spec.ts | 2 +- .../workflow/workflowitem_history_get.ts | 18 +- .../domain/workflow/workflowitem_list.spec.ts | 2 +- .../domain/workflow/workflowitem_list.ts | 14 +- .../workflow/workflowitem_ordering.spec.ts | 2 +- .../domain/workflow/workflowitem_ordering.ts | 9 +- .../workflowitem_permission_grant.spec.ts | 2 +- .../workflow/workflowitem_permission_grant.ts | 19 +- .../workflowitem_permission_revoke.spec.ts | 2 +- .../workflowitem_permission_revoke.ts | 24 +- .../workflowitem_permissions_list.spec.ts | 2 +- .../workflow/workflowitem_permissions_list.ts | 4 +- .../workflow/workflowitem_update.spec.ts | 2 +- .../domain/workflow/workflowitem_update.ts | 12 +- .../domain/workflow/workflowitem_updated.ts | 6 +- .../domain/workflow/workflowitems_reorder.ts | 15 +- .../apply_workflowitem_type.ts | 9 +- .../domain/workflowitem_types/restricted.ts | 2 +- .../workflowitem_type.spec.ts | 2 +- .../service/errors/authentication_failed.ts | 2 +- api/src/service/eventsourcing.ts | 374 -- api/src/service/global_permission_grant.ts | 2 + api/src/service/global_permission_revoke.ts | 2 + api/src/service/global_permissions_get.ts | 2 + api/src/service/grantpermissiontoaddress.ts | 2 + api/src/service/group_create.ts | 4 + api/src/service/group_member_add.ts | 3 + api/src/service/group_member_remove.ts | 7 +- api/src/service/group_permissions_list.ts | 3 + api/src/service/group_query.ts | 25 +- api/src/service/groups.ts | 29 +- api/src/service/hexconverter.ts | 9 +- api/src/service/importprivkey.ts | 1 + api/src/service/index.ts | 225 +- api/src/service/notification_list.ts | 3 + api/src/service/notification_mark_read.ts | 6 + api/src/service/project_assign.ts | 4 + api/src/service/project_close.ts | 3 + api/src/service/project_create.ts | 3 + api/src/service/project_get.ts | 10 +- api/src/service/project_history_get.ts | 4 + api/src/service/project_list.ts | 4 + api/src/service/project_permission_grant.ts | 5 + api/src/service/project_permission_revoke.ts | 5 + api/src/service/project_permissions_list.ts | 8 +- .../project_projected_budget_delete.ts | 5 + .../project_projected_budget_update.ts | 5 + api/src/service/project_update.ts | 5 + api/src/service/provisioning_end.ts | 3 + api/src/service/provisioning_get.ts | 4 + api/src/service/provisioning_start.ts | 3 + api/src/service/public_key_get.ts | 3 + api/src/service/public_key_publish.ts | 2 + api/src/service/public_key_update.ts | 3 + api/src/service/storage_service_url_get.ts | 3 + api/src/service/storage_service_url_update.ts | 3 + api/src/service/subproject_assign.ts | 3 + api/src/service/subproject_close.ts | 3 + api/src/service/subproject_create.ts | 3 + api/src/service/subproject_get.ts | 3 + api/src/service/subproject_history_get.ts | 3 + api/src/service/subproject_list.ts | 3 + .../service/subproject_permission_grant.ts | 3 + .../service/subproject_permission_revoke.ts | 5 + .../service/subproject_permissions_list.ts | 3 + .../subproject_projected_budget_delete.ts | 3 + .../subproject_projected_budget_update.ts | 9 + api/src/service/subproject_update.ts | 4 + api/src/service/user_assignments_get.ts | 3 + api/src/service/user_authenticate.ts | 15 +- api/src/service/user_create.ts | 4 + api/src/service/user_disable.ts | 4 + api/src/service/user_enable.ts | 21 +- api/src/service/user_password_change.ts | 4 + api/src/service/user_permission_grant.ts | 3 + api/src/service/user_permission_revoke.ts | 3 + api/src/service/user_permissions_list.ts | 5 + api/src/service/user_query.ts | 8 + api/src/service/workflowitem_assign.ts | 6 + api/src/service/workflowitem_close.ts | 3 + api/src/service/workflowitem_create.ts | 6 +- .../service/workflowitem_document_download.ts | 3 + api/src/service/workflowitem_get.ts | 4 + api/src/service/workflowitem_get_details.ts | 3 + api/src/service/workflowitem_history_get.ts | 4 + api/src/service/workflowitem_list.ts | 3 + .../service/workflowitem_permission_grant.ts | 8 + .../service/workflowitem_permission_revoke.ts | 8 + .../service/workflowitem_permissions_list.ts | 4 + api/src/service/workflowitem_update.ts | 16 +- api/src/service/workflowitems_reorder.ts | 3 + api/src/subproject_assign.ts | 2 + api/src/subproject_budget_delete_projected.ts | 2 + api/src/subproject_budget_update_projected.ts | 2 + api/src/subproject_close.ts | 2 + api/src/subproject_create.ts | 2 + api/src/subproject_list.ts | 5 +- api/src/subproject_permission_grant.ts | 2 + api/src/subproject_permission_revoke.ts | 2 + api/src/subproject_permissions_list.ts | 14 +- api/src/subproject_update.ts | 2 + api/src/subproject_view_details.ts | 13 +- api/src/subproject_view_history.ts | 21 +- api/src/subproject_view_history_v2.ts | 78 +- api/src/user_authenticate.ts | 3 + api/src/user_create.ts | 3 + api/src/user_disable.ts | 2 + api/src/user_enable.ts | 2 + api/src/user_list.ts | 1 + api/src/user_listAssignments.ts | 5 +- api/src/user_password_change.ts | 2 + api/src/user_permission_grant.ts | 2 + api/src/user_permission_revoke.ts | 2 + api/src/user_permissions_list.ts | 5 +- api/src/workflowitem_assign.ts | 2 + api/src/workflowitem_close.ts | 2 + api/src/workflowitem_create.ts | 2 + api/src/workflowitem_download_document.ts | 20 +- api/src/workflowitem_list.ts | 15 +- api/src/workflowitem_permission_grant.ts | 2 + api/src/workflowitem_permission_revoke.ts | 2 + api/src/workflowitem_permissions_list.ts | 19 +- api/src/workflowitem_update.ts | 2 + api/src/workflowitem_validate_document.ts | 2 + api/src/workflowitem_view_details.ts | 18 +- api/src/workflowitem_view_history.ts | 86 +- api/src/workflowitems_reorder.ts | 2 + api/tsconfig.json | 8 +- 362 files changed, 2727 insertions(+), 4883 deletions(-) delete mode 100644 api/src/service/SubprojectEvents.ts delete mode 100644 api/src/service/User.ts delete mode 100644 api/src/service/Workflowitem.ts delete mode 100644 api/src/service/eventsourcing.ts diff --git a/api/Dockerfile b/api/Dockerfile index 0345a914f..ff0c7da86 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -8,7 +8,6 @@ RUN npm ci COPY src src/ COPY tsconfig.json . - RUN npm run build ARG BUILDTIMESTAMP='' diff --git a/api/README.md b/api/README.md index 4b5305715..7ebbfe6ab 100644 --- a/api/README.md +++ b/api/README.md @@ -8,7 +8,7 @@ | ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | | ORGANIZATION_VAULT_SECRET | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! | | PORT | no | 8080 | The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | -| PRETTY_PRINT | no | true | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | +| PRETTY_PRINT | no | false | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | | ROOT_SECRET | no | [random] | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | | RPC_HOST | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | | BACKUP_API_PORT | no | 8085 | The Port of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | diff --git a/api/package-lock.json b/api/package-lock.json index af5760d60..1f333de8f 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "axios": "^0.21.4", "bcryptjs": "^2.4.3", - "concurrently": "^6.2.2", "fastify": "^3.11.0", "fastify-cors": "^6.0.2", "fastify-helmet": "^5.3.2", @@ -21,9 +20,10 @@ "joi": "^14.3.1", "jsonwebtoken": "^8.5.0", "lodash.isequal": "^4.5.0", + "module-alias": "^2.2.2", "raw-body": "^2.3.3", "sodium-native": "^3.3.0", - "trubudget-logging-service": "^1.1.2", + "trubudget-logging-service": "^1.1.3", "uuid": "^3.2.1", "verror": "^1.10.0" }, @@ -52,14 +52,14 @@ "lodash.isempty": "^4.4.0", "mocha": "^9.1.2", "mocha-lcov-reporter": "^1.3.0", - "nodemon": "^2.0.3", "nyc": "^15.1.0", "openapi-typescript": "^2.4.2", - "rimraf": "*", "sinon": "^9.2.4", "supertest": "^4.0.2", "swagger-to-joi": "^1.2.4", - "ts-node": "^8.0.3", + "ts-node": "^10.4.0", + "ts-node-dev": "^1.1.8", + "tsconfig-paths": "^3.11.0", "tslint": "*", "typescript": "^4.0.2" } @@ -384,33 +384,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dev": true, - "peer": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz", - "integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==", - "dev": true, - "peer": true, - "dependencies": { - "core-js-pure": "^3.16.0", - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", @@ -505,6 +478,27 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -587,6 +581,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "dependencies": { + "ajv": "^6.12.6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -778,15 +780,6 @@ "node": ">= 8" } }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -822,17 +815,29 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true }, "node_modules/@types/bcryptjs": { "version": "2.4.2", @@ -846,12 +851,6 @@ "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", "dev": true }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, "node_modules/@types/joi": { "version": "14.3.3", "resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.3.3.tgz", @@ -955,6 +954,18 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "node_modules/@types/uuid": { "version": "3.4.5", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", @@ -1279,12 +1290,6 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "node_modules/abstract-logging": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.0.tgz", @@ -1311,6 +1316,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -1339,15 +1353,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1361,6 +1366,7 @@ "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" } @@ -1435,20 +1441,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, "node_modules/array-includes": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", @@ -1493,24 +1485,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", - "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -1546,13 +1520,6 @@ "node": "*" } }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true, - "peer": true - }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1616,16 +1583,6 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, - "node_modules/axe-core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz", - "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -1634,13 +1591,6 @@ "follow-redirects": "^1.14.0" } }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true, - "peer": true - }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1674,105 +1624,6 @@ "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/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/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1829,9 +1680,9 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "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/builtin-modules": { @@ -1851,45 +1702,6 @@ "node": ">= 0.8" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -2037,12 +1849,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2052,34 +1858,17 @@ "node": ">=6" } }, - "node_modules/cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "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/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2142,146 +1931,30 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "node_modules/concurrently": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.3.0.tgz", - "integrity": "sha512-k4k1jQGHHKsfbqzkUszVf29qECBrkvBKkcPJEUDTyVR7tZd1G/JOfnst4g1sYbFvJ4UjHZisj1aWQR8yLKpGPw==", - "dependencies": { - "chalk": "^4.1.0", - "date-fns": "^2.16.1", - "lodash": "^4.17.21", - "rxjs": "^6.6.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^16.2.0" - }, - "bin": { - "concurrently": "bin/concurrently.js" - }, + "node_modules/confusing-browser-globals": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", + "dev": true + }, + "node_modules/contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true, "engines": { - "node": ">=10.0.0" + "node": ">=0.10.0" } }, - "node_modules/concurrently/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==", + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", "dependencies": { - "color-convert": "^2.0.1" + "safe-buffer": "5.1.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/concurrently/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/concurrently/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/concurrently/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", - "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", - "dev": true - }, - "node_modules/contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" + "node": ">= 0.6" } }, "node_modules/content-disposition/node_modules/safe-buffer": { @@ -2318,18 +1991,6 @@ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", "dev": true }, - "node_modules/core-js-pure": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.1.tgz", - "integrity": "sha512-kmW/k8MaSuqpvA1xm2l3TVlBuvW+XBkcaOroFUpO3D4lsTGQWBTb/tBDCf/PNkkPLrwgrkQRIYNPB0CeqGJWGQ==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2355,6 +2016,12 @@ "node": ">=4.0.0" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2369,22 +2036,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz", - "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==", - "dev": true, - "peer": true - }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2397,18 +2048,6 @@ "node": ">=0.10" } }, - "node_modules/date-fns": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz", - "integrity": "sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==", - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/dateformat": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz", @@ -2457,18 +2096,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -2481,15 +2108,6 @@ "node": ">=0.12" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -2525,12 +2143,6 @@ "node": ">=8" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2598,24 +2210,15 @@ "node": ">=6.0.0" } }, - "node_modules/dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", "dev": true, "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" + "xtend": "^4.0.0" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -2648,7 +2251,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/encodeurl": { "version": "1.0.2", @@ -2774,17 +2378,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", "dev": true, "engines": { - "node": ">=8" + "node": ">=6" } }, "node_modules/escape-html": { @@ -3194,39 +2790,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", - "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.11.2", - "aria-query": "^4.2.2", - "array-includes": "^3.1.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.0.2", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", - "has": "^1.0.3", - "jsx-ast-utils": "^3.1.0", - "language-tags": "^1.0.5" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true - }, "node_modules/eslint-plugin-promise": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", @@ -3236,85 +2799,6 @@ "node": ">=6" } }, - "node_modules/eslint-plugin-react": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz", - "integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==", - "dev": true, - "peer": true, - "dependencies": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "estraverse": "^5.2.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.5" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "peer": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", - "dev": true, - "peer": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -3669,12 +3153,13 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "node_modules/fast-json-stringify": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.2.4.tgz", - "integrity": "sha512-p1IkIBKbro/bGQUl0sVJYvJp/GpQZeqB/6xTDFliKfosNhCZun5FWZPnIfl0NMQYENDf81jMn83PR0JYhg2TwQ==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", "dependencies": { "ajv": "^6.11.0", "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", "string-similarity": "^4.0.1" }, "engines": { @@ -3725,29 +3210,25 @@ } }, "node_modules/fastify": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.11.0.tgz", - "integrity": "sha512-xc0mTG3cZhBrpsHh5M+xujA/xAIfBCn6DLOWPqCbIBkt0TtfeDaXpBEtlejWp13eM8SZbyXnHcR6PJaOajbY/A==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", "dependencies": { + "@fastify/ajv-compiler": "^1.0.0", "abstract-logging": "^2.0.0", - "ajv": "^6.12.2", "avvio": "^7.1.2", - "fast-json-stringify": "^2.2.1", + "fast-json-stringify": "^2.5.2", "fastify-error": "^0.3.0", "fastify-warning": "^0.2.0", - "find-my-way": "^3.0.5", + "find-my-way": "^4.1.0", "flatstr": "^1.0.12", "light-my-request": "^4.2.0", - "pino": "^6.2.1", - "proxy-addr": "^2.0.5", - "readable-stream": "^3.4.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", "rfdc": "^1.1.4", "secure-json-parse": "^2.0.0", "semver": "^7.3.2", "tiny-lru": "^7.0.0" - }, - "engines": { - "node": ">=10.16.0" } }, "node_modules/fastify-cors": { @@ -4077,11 +3558,12 @@ } }, "node_modules/find-my-way": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-3.0.5.tgz", - "integrity": "sha512-FweGg0cv1sBX8z7WhvBX5B5AECW4Zdh/NiB38Oa0qwSNIyPgRBCl/YjxuZn/rz38E/MMBHeVKJ22i7W3c626Gg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", "dependencies": { "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", "safe-regex2": "^2.0.0", "semver-store": "^0.3.0" }, @@ -4193,13 +3675,13 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", "integrity": "sha1-cPt8oCkO5v+WEJBBX0s989IIJlk=", - "deprecated": "Please upgrade to the upcoming v2, currently (until end of February) install using formidable@canary!", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", "dev": true }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -4276,6 +3758,7 @@ "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.*" } @@ -4321,18 +3804,6 @@ "node": ">=4" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -4389,18 +3860,6 @@ "node": ">= 6" } }, - "node_modules/global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "dev": true, - "dependencies": { - "ini": "^1.3.5" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -4439,28 +3898,6 @@ "node": ">= 4" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/graceful-fs": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", @@ -4564,15 +4001,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -4624,12 +4052,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "node_modules/http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -4680,12 +4102,6 @@ "node": ">= 4" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4702,15 +4118,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4743,12 +4150,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "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/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -4829,18 +4230,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", @@ -4875,6 +4264,7 @@ "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" } @@ -4891,22 +4281,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -4919,15 +4293,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4952,24 +4317,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "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", @@ -5085,12 +4432,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5324,12 +4665,6 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "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", @@ -5463,39 +4798,6 @@ "node >=0.6.0" ] }, - "node_modules/jsx-ast-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", - "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", - "dev": true, - "peer": true, - "dependencies": { - "array-includes": "^3.1.3", - "object.assign": "^4.1.2" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/jsx-ast-utils/node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/just-extend": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", @@ -5521,15 +4823,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -5539,35 +4832,6 @@ "node": ">=0.10.0" } }, - "node_modules/language-subtag-registry": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", - "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==", - "dev": true, - "peer": true - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", - "dev": true, - "peer": true, - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lcov-parse": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", @@ -5644,7 +4908,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -5817,28 +5082,6 @@ "node": ">=8" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6198,15 +5441,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6476,6 +5710,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, "node_modules/mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -6555,71 +5794,6 @@ "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", "dev": true }, - "node_modules/nodemon": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.3.tgz", - "integrity": "sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^4.0.0" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -6650,15 +5824,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -6887,16 +6052,6 @@ "node": "*" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", @@ -6944,38 +6099,6 @@ "node": ">= 0.4" } }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", - "dev": true, - "peer": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -7135,15 +6258,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -7204,21 +6318,6 @@ "node": ">=8" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7442,15 +6541,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/prettier": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", @@ -7501,24 +6591,12 @@ "node": ">=10" } }, - "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { @@ -7531,12 +6609,6 @@ "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true }, - "node_modules/pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha1-x2ljooBH7WFULcNhqibuVaf6FfM=", - "dev": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -7554,18 +6626,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -7659,28 +6719,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "peer": true - }, "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -7758,30 +6796,6 @@ "node": ">=8" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true, - "peer": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -7794,37 +6808,13 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" + "es6-error": "^4.0.1" }, "engines": { "node": ">=4" @@ -7875,6 +6865,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7912,15 +6903,6 @@ "node": ">=4" } }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/ret": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", @@ -7978,17 +6960,6 @@ } ] }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, "node_modules/safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -8021,18 +6992,6 @@ "semver": "bin/semver.js" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/semver-store": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", @@ -8291,9 +7250,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "dependencies": { "buffer-from": "^1.0.0", @@ -8303,17 +7262,12 @@ "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" - }, "node_modules/spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -8431,14 +7385,15 @@ } }, "node_modules/string-similarity": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.2.tgz", - "integrity": "sha512-eCsPPyoQBgY4TMpVD6DVfO7pLrimUONriaO4Xjp3WPUW0YnNLqdHgRj23xotLlqrL90eJhBeq3zdAJf2mQgfBQ==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" }, "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", @@ -8448,26 +7403,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", - "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -8498,6 +7433,7 @@ "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" }, @@ -8663,18 +7599,6 @@ "bintrees": "1.0.1" } }, - "node_modules/term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -8712,15 +7636,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8750,18 +7665,6 @@ "hoek": "6.x.x" } }, - "node_modules/touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "dependencies": { - "nopt": "~1.0.10" - }, - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -8785,6 +7688,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, "bin": { "tree-kill": "cli.js" } @@ -8799,35 +7703,170 @@ } }, "node_modules/trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "dependencies": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" } }, + "node_modules/trubudget-logging-service/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, "node_modules/ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", + "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^9.0.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ts-node-dev/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ts-node-dev/node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, "dependencies": { "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "source-map-support": "^0.5.17", + "yn": "3.1.1" }, "bin": { - "ts-node": "dist/bin.js" + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" }, "engines": { - "node": ">=4.2.0" + "node": ">=10.0.0" }, "peerDependencies": { - "typescript": ">=2.0" + "typescript": ">=2.7" + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/ts-node/node_modules/diff": { @@ -8839,10 +7878,22 @@ "node": ">=0.3.1" } }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, "node_modules/tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -8854,7 +7905,8 @@ "node_modules/tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=" + "integrity": "sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=", + "dev": true }, "node_modules/tslint": { "version": "5.18.0", @@ -9001,36 +8053,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "dependencies": { - "debug": "^2.2.0" - } - }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -9039,101 +8061,6 @@ "node": ">= 0.8" } }, - "node_modules/update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", - "dev": true, - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/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/update-notifier/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/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -9142,18 +8069,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9242,18 +8157,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -9273,6 +8176,7 @@ "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", @@ -9289,6 +8193,7 @@ "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" }, @@ -9303,6 +8208,7 @@ "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" }, @@ -9313,7 +8219,8 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/wrappy": { "version": "1.0.2", @@ -9325,20 +8232,11 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, "node_modules/xtend": { @@ -9353,6 +8251,7 @@ "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" } @@ -9366,6 +8265,7 @@ "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", @@ -9383,6 +8283,7 @@ "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, "engines": { "node": ">=10" } @@ -9695,27 +8596,6 @@ "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", "dev": true }, - "@babel/runtime": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", - "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", - "dev": true, - "peer": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz", - "integrity": "sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg==", - "dev": true, - "peer": true, - "requires": { - "core-js-pure": "^3.16.0", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", @@ -9791,6 +8671,21 @@ "to-fast-properties": "^2.0.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -9846,6 +8741,14 @@ } } }, + "@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "requires": { + "ajv": "^6.12.6" + } + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -9988,12 +8891,6 @@ "fastq": "^1.6.0" } }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, "@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -10029,14 +8926,29 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true }, "@types/bcryptjs": { "version": "2.4.2", @@ -10050,12 +8962,6 @@ "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", "dev": true }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, "@types/joi": { "version": "14.3.3", "resolved": "https://registry.npmjs.org/@types/joi/-/joi-14.3.3.tgz", @@ -10161,6 +9067,18 @@ "@types/node": "*" } }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "@types/uuid": { "version": "3.4.5", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", @@ -10362,12 +9280,6 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "abstract-logging": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.0.tgz", @@ -10386,6 +9298,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -10407,15 +9325,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -10425,7 +9334,8 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -10485,17 +9395,6 @@ "mri": "1.1.4" } }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, "array-includes": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", @@ -10525,18 +9424,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "array.prototype.flatmap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", - "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - } - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -10563,13 +9450,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true, - "peer": true - }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -10625,13 +9505,6 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, - "axe-core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz", - "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==", - "dev": true, - "peer": true - }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -10640,13 +9513,6 @@ "follow-redirects": "^1.14.0" } }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true, - "peer": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -10677,80 +9543,6 @@ "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, - "boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -10794,9 +9586,9 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "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 }, "builtin-modules": { @@ -10810,38 +9602,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -10952,43 +9712,23 @@ "readdirp": "~3.6.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", - "dev": true - }, "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, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -11011,124 +9751,40 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concurrently": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.3.0.tgz", - "integrity": "sha512-k4k1jQGHHKsfbqzkUszVf29qECBrkvBKkcPJEUDTyVR7tZd1G/JOfnst4g1sYbFvJ4UjHZisj1aWQR8yLKpGPw==", - "requires": { - "chalk": "^4.1.0", - "date-fns": "^2.16.1", - "lodash": "^4.17.21", - "rxjs": "^6.6.3", - "spawn-command": "^0.0.2-1", - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^16.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "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==", - "requires": { - "color-name": "~1.1.4" - } - }, - "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==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } + "dev": true }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" + "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, "confusing-browser-globals": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", @@ -11184,13 +9840,6 @@ "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", "dev": true }, - "core-js-pure": { - "version": "3.18.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.18.1.tgz", - "integrity": "sha512-kmW/k8MaSuqpvA1xm2l3TVlBuvW+XBkcaOroFUpO3D4lsTGQWBTb/tBDCf/PNkkPLrwgrkQRIYNPB0CeqGJWGQ==", - "dev": true, - "peer": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -11210,6 +9859,12 @@ "request": "^2.86.0" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -11221,19 +9876,6 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "damerau-levenshtein": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz", - "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==", - "dev": true, - "peer": true - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -11243,11 +9885,6 @@ "assert-plus": "^1.0.0" } }, - "date-fns": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz", - "integrity": "sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w==" - }, "dateformat": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz", @@ -11286,15 +9923,6 @@ } } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -11304,12 +9932,6 @@ "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -11338,12 +9960,6 @@ } } }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -11393,21 +10009,15 @@ "esutils": "^2.0.2" } }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", "dev": true, "requires": { - "is-obj": "^2.0.0" + "xtend": "^4.0.0" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -11440,7 +10050,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "encodeurl": { "version": "1.0.2", @@ -11540,12 +10151,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-html": { @@ -11937,102 +10543,12 @@ } } }, - "eslint-plugin-jsx-a11y": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", - "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", - "dev": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.11.2", - "aria-query": "^4.2.2", - "array-includes": "^3.1.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.0.2", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", - "has": "^1.0.3", - "jsx-ast-utils": "^3.1.0", - "language-tags": "^1.0.5" - }, - "dependencies": { - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true - } - } - }, "eslint-plugin-promise": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "dev": true }, - "eslint-plugin-react": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz", - "integrity": "sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==", - "dev": true, - "peer": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flatmap": "^1.2.4", - "doctrine": "^2.1.0", - "estraverse": "^5.2.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.0.4", - "object.entries": "^1.1.4", - "object.fromentries": "^2.0.4", - "object.hasown": "^1.0.0", - "object.values": "^1.1.4", - "prop-types": "^15.7.2", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.5" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "peer": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "peer": true - }, - "resolve": { - "version": "2.0.0-next.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", - "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", - "dev": true, - "peer": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } - } - }, - "eslint-plugin-react-hooks": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", - "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true, - "peer": true, - "requires": {} - }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -12185,12 +10701,13 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-json-stringify": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.2.4.tgz", - "integrity": "sha512-p1IkIBKbro/bGQUl0sVJYvJp/GpQZeqB/6xTDFliKfosNhCZun5FWZPnIfl0NMQYENDf81jMn83PR0JYhg2TwQ==", + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", "requires": { "ajv": "^6.11.0", "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", "string-similarity": "^4.0.1" } }, @@ -12234,22 +10751,21 @@ } }, "fastify": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.11.0.tgz", - "integrity": "sha512-xc0mTG3cZhBrpsHh5M+xujA/xAIfBCn6DLOWPqCbIBkt0TtfeDaXpBEtlejWp13eM8SZbyXnHcR6PJaOajbY/A==", + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", "requires": { + "@fastify/ajv-compiler": "^1.0.0", "abstract-logging": "^2.0.0", - "ajv": "^6.12.2", "avvio": "^7.1.2", - "fast-json-stringify": "^2.2.1", + "fast-json-stringify": "^2.5.2", "fastify-error": "^0.3.0", "fastify-warning": "^0.2.0", - "find-my-way": "^3.0.5", + "find-my-way": "^4.1.0", "flatstr": "^1.0.12", "light-my-request": "^4.2.0", - "pino": "^6.2.1", - "proxy-addr": "^2.0.5", - "readable-stream": "^3.4.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", "rfdc": "^1.1.4", "secure-json-parse": "^2.0.0", "semver": "^7.3.2", @@ -12542,11 +11058,12 @@ } }, "find-my-way": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-3.0.5.tgz", - "integrity": "sha512-FweGg0cv1sBX8z7WhvBX5B5AECW4Zdh/NiB38Oa0qwSNIyPgRBCl/YjxuZn/rz38E/MMBHeVKJ22i7W3c626Gg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", "requires": { "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", "safe-regex2": "^2.0.0", "semver-store": "^0.3.0" } @@ -12626,9 +11143,9 @@ "dev": true }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { "version": "0.5.2", @@ -12674,7 +11191,8 @@ "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==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-func-name": { "version": "2.0.0", @@ -12705,15 +11223,6 @@ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -12755,15 +11264,6 @@ "is-glob": "^4.0.1" } }, - "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "dev": true, - "requires": { - "ini": "^1.3.5" - } - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -12792,25 +11292,6 @@ } } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, "graceful-fs": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", @@ -12880,12 +11361,6 @@ "has-symbols": "^1.0.2" } }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true - }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -12924,12 +11399,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -12967,12 +11436,6 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", - "dev": true - }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -12983,12 +11446,6 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -13015,12 +11472,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -13077,15 +11528,6 @@ "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-core-module": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", @@ -13110,7 +11552,8 @@ "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==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-glob": { "version": "4.0.1", @@ -13121,28 +11564,12 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", - "dev": true, - "requires": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - } - }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", "dev": true }, - "is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -13158,18 +11585,6 @@ "has-tostringtag": "^1.0.0" } }, - "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 - }, - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -13243,12 +11658,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -13432,12 +11841,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "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", @@ -13550,32 +11953,6 @@ } } }, - "jsx-ast-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", - "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", - "dev": true, - "peer": true, - "requires": { - "array-includes": "^3.1.3", - "object.assign": "^4.1.2" - }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } - } - }, "just-extend": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", @@ -13601,47 +11978,12 @@ "safe-buffer": "^5.0.1" } }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, "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 }, - "language-subtag-registry": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", - "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==", - "dev": true, - "peer": true - }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", - "dev": true, - "peer": true, - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "lcov-parse": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", @@ -13706,7 +12048,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "lodash.clonedeep": { "version": "4.5.0", @@ -13851,22 +12194,6 @@ } } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -14130,12 +12457,6 @@ "mime-db": "1.40.0" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -14324,6 +12645,11 @@ "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", "dev": true }, + "module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, "mri": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", @@ -14374,68 +12700,18 @@ "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, - "node-releases": { - "version": "1.1.77", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", - "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", - "dev": true - }, - "nodemon": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.3.tgz", - "integrity": "sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw==", - "dev": true, - "requires": { - "chokidar": "^3.2.2", - "debug": "^3.2.6", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", - "pstree.remy": "^1.1.7", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.2", - "update-notifier": "^4.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { - "abbrev": "1" + "process-on-spawn": "^1.0.0" } }, + "node-releases": { + "version": "1.1.77", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", + "integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -14462,12 +12738,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -14647,13 +12917,6 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "peer": true - }, "object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", @@ -14689,29 +12952,6 @@ "es-abstract": "^1.19.1" } }, - "object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, - "object.hasown": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", - "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", - "dev": true, - "peer": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - } - }, "object.values": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", @@ -14831,12 +13071,6 @@ "word-wrap": "^1.2.3" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -14882,18 +13116,6 @@ "release-zalgo": "^1.0.0" } }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -15075,12 +13297,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "prettier": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", @@ -15116,24 +13332,12 @@ "tdigest": "^0.1.1" } }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "peer": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -15143,12 +13347,6 @@ "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true }, - "pstree.remy": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", - "integrity": "sha1-x2ljooBH7WFULcNhqibuVaf6FfM=", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -15163,15 +13361,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -15238,25 +13427,6 @@ } } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "peer": true - }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -15318,48 +13488,12 @@ "strip-indent": "^3.0.0" } }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true, - "peer": true - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, - "registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -15408,7 +13542,8 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-from-string": { "version": "2.0.2", @@ -15437,15 +13572,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "ret": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", @@ -15476,14 +13602,6 @@ "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", "dev": true }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "requires": { - "tslib": "^1.9.0" - } - }, "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -15513,15 +13631,6 @@ "integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=", "dev": true }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, "semver-store": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", @@ -15735,9 +13844,9 @@ "dev": true }, "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -15747,16 +13856,11 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, - "spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" - }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -15860,37 +13964,21 @@ } }, "string-similarity": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.2.tgz", - "integrity": "sha512-eCsPPyoQBgY4TMpVD6DVfO7pLrimUONriaO4Xjp3WPUW0YnNLqdHgRj23xotLlqrL90eJhBeq3zdAJf2mQgfBQ==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" }, "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, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, - "string.prototype.matchall": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", - "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.3.1", - "side-channel": "^1.0.4" - } - }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -15915,6 +14003,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -16056,12 +14145,6 @@ "bintrees": "1.0.1" } }, - "term-size": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", - "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==", - "dev": true - }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16090,12 +14173,6 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16118,15 +14195,6 @@ "hoek": "6.x.x" } }, - "touch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", - "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", - "dev": true, - "requires": { - "nopt": "~1.0.10" - } - }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -16148,7 +14216,8 @@ "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true }, "trim-newlines": { "version": "3.0.1", @@ -16157,28 +14226,54 @@ "dev": true }, "trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "requires": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" + }, + "dependencies": { + "axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "requires": { + "follow-redirects": "^1.14.4" + } + } } }, "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", - "dev": true, - "requires": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "yn": "3.1.1" }, "dependencies": { + "acorn": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", + "dev": true + }, "diff": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", @@ -16187,10 +14282,77 @@ } } }, + "ts-node-dev": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", + "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", + "dev": true, + "requires": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^9.0.0", + "tsconfig": "^7.0.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + } + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", + "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -16202,7 +14364,8 @@ "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=" + "integrity": "sha1-w8GflZc/sKYpc/sJ2Q2WHuQ+XIo=", + "dev": true }, "tslint": { "version": "5.18.0", @@ -16311,113 +14474,11 @@ "which-boxed-primitive": "^1.0.2" } }, - "undefsafe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", - "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", - "dev": true, - "requires": { - "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "update-notifier": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", - "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", - "dev": true, - "requires": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -16426,15 +14487,6 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16504,15 +14556,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -16529,6 +14572,7 @@ "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, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16539,6 +14583,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -16547,6 +14592,7 @@ "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, "requires": { "color-name": "~1.1.4" } @@ -16554,7 +14600,8 @@ "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==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, @@ -16575,12 +14622,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -16589,7 +14630,8 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, "yallist": { "version": "4.0.0", @@ -16600,6 +14642,7 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", @@ -16613,7 +14656,8 @@ "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { "version": "2.0.0", diff --git a/api/package.json b/api/package.json index cb10b8522..695f7e1bc 100644 --- a/api/package.json +++ b/api/package.json @@ -36,9 +36,9 @@ "scripts": { "start": "node dist/", "build": "rimraf dist && tsc", - "dev": "rimraf dist && tsc && concurrently \"tsc -w --preserveWatchOutput\" \"nodemon -q dist/index.js -- --inspect=0.0.0.0:9229\"", - "watch": "tsc && concurrently \"tsc -w --preserveWatchOutput\" \"nodemon -q dist/index.js -- --inspect=0.0.0.0:9229\"", - "test": "nyc mocha --require ts-node/register \"src/**/*.spec.ts\"", + "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts -- --inspect=0.0.0.0:9229", + "watch": "ts-node-dev -r tsconfig-paths/register src/index.ts -- --inspect=0.0.0.0:9229", + "test": "nyc mocha --require ts-node/register -r tsconfig-paths/register \"src/**/*.spec.ts\"", "coverage": "nyc report --reporter=text-lcov | coveralls", "generate-report": "nyc report --reporter=html", "lint": "eslint src --ext ts", @@ -71,7 +71,6 @@ "dependencies": { "axios": "^0.21.4", "bcryptjs": "^2.4.3", - "concurrently": "^6.2.2", "fastify": "^3.11.0", "fastify-cors": "^6.0.2", "fastify-helmet": "^5.3.2", @@ -81,9 +80,10 @@ "joi": "^14.3.1", "jsonwebtoken": "^8.5.0", "lodash.isequal": "^4.5.0", + "module-alias": "^2.2.2", "raw-body": "^2.3.3", "sodium-native": "^3.3.0", - "trubudget-logging-service": "^1.1.2", + "trubudget-logging-service": "^1.1.3", "uuid": "^3.2.1", "verror": "^1.10.0" }, @@ -112,15 +112,18 @@ "lodash.isempty": "^4.4.0", "mocha": "^9.1.2", "mocha-lcov-reporter": "^1.3.0", - "nodemon": "^2.0.3", "nyc": "^15.1.0", "openapi-typescript": "^2.4.2", - "rimraf": "*", "sinon": "^9.2.4", "supertest": "^4.0.2", "swagger-to-joi": "^1.2.4", - "ts-node": "^8.0.3", + "ts-node": "^10.4.0", + "ts-node-dev": "^1.1.8", + "tsconfig-paths": "^3.11.0", "tslint": "*", "typescript": "^4.0.2" + }, + "_moduleAliases": { + "lib": "dist/lib" } } diff --git a/api/src/config.ts b/api/src/config.ts index 2d9dbe316..aa89c1b64 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -13,7 +13,6 @@ interface ProcessEnvVars { RPC_PASSWORD: string; BACKUP_API_PORT: string; JWT_SECRET: string; - PRETTY_PRINT: string; CI_COMMIT_SHA: string; BUILDTIMESTAMP: string; DOCUMENT_FEATURE_ENABLED: string; @@ -41,7 +40,6 @@ interface Config { port: number; }; jwtSecret: string; - prettyPrint: boolean; version: string; // Continues Integration commit: string; @@ -80,7 +78,6 @@ export const config: Config = { port: Number(process.env.BACKUP_API_PORT) || 8085, }, jwtSecret: process.env.JWT_SECRET || randomString(32), - prettyPrint: process.env.PRETTY_PRINT === "true" ? true : false, version: process.env.npm_package_version || "", // Continues Integration commit: process.env.CI_COMMIT_SHA || "", diff --git a/api/src/global_permission_grant.ts b/api/src/global_permission_grant.ts index e7dad43ab..8ce40daa1 100644 --- a/api/src/global_permission_grant.ts +++ b/api/src/global_permission_grant.ts @@ -107,6 +107,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi new VError(bodyResult, "failed to grant global permission"), ); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid request body"); return; } @@ -126,6 +127,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi .catch((err) => { const { code, body } = toHttpError(err); reply.status(code).send(body); + request.log.error({ err }, "Error while granting global permission"); }); }); } diff --git a/api/src/global_permission_revoke.ts b/api/src/global_permission_revoke.ts index 08a29533d..0bbcbc3f2 100644 --- a/api/src/global_permission_revoke.ts +++ b/api/src/global_permission_revoke.ts @@ -107,6 +107,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi new VError(bodyResult, "failed to revoke global permission"), ); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid request body"); return; } @@ -125,6 +126,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while revoking global permission"); reply.status(code).send(body); }); }); diff --git a/api/src/global_permissions_grant_all.ts b/api/src/global_permissions_grant_all.ts index df66b98f4..6f009c236 100644 --- a/api/src/global_permissions_grant_all.ts +++ b/api/src/global_permissions_grant_all.ts @@ -111,6 +111,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to grant all global permissions"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -149,6 +150,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while granting all global permissions"); reply.status(code).send(body); } }, diff --git a/api/src/global_permissions_list.ts b/api/src/global_permissions_list.ts index f5c528c1b..daaa9fa51 100644 --- a/api/src/global_permissions_list.ts +++ b/api/src/global_permissions_list.ts @@ -68,6 +68,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while fetching global permisions"); reply.status(code).send(body); } }, diff --git a/api/src/group_create.ts b/api/src/group_create.ts index a5e2cf67f..b7b6feaa1 100644 --- a/api/src/group_create.ts +++ b/api/src/group_create.ts @@ -145,7 +145,9 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to create group")); + reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid request body"); return; } @@ -157,6 +159,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi break; } default: + request.log.error({ err: bodyResult }, "Invalid Api Version specified"); // Joi validates only existing apiVersions assertUnreachable(bodyResult.apiVersion); } @@ -176,7 +179,9 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + reply.status(code).send(body); + request.log.error({ err }, "Error while creating group"); }); }); } diff --git a/api/src/group_list.ts b/api/src/group_list.ts index 88df841c2..45335d838 100644 --- a/api/src/group_list.ts +++ b/api/src/group_list.ts @@ -101,6 +101,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while fetching all groups"); reply.status(code).send(body); }); }); diff --git a/api/src/group_member_add.ts b/api/src/group_member_add.ts index 9bed1f302..9ef5d5bf9 100644 --- a/api/src/group_member_add.ts +++ b/api/src/group_member_add.ts @@ -101,6 +101,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to add user to group")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -129,6 +130,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while adding user to group"); reply.status(code).send(body); }); }); diff --git a/api/src/group_member_remove.ts b/api/src/group_member_remove.ts index d1d8e7ffc..75ebf34a2 100644 --- a/api/src/group_member_remove.ts +++ b/api/src/group_member_remove.ts @@ -103,6 +103,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to remove user from group"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -131,6 +132,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while removing user from group"); reply.status(code).send(body); }); }); diff --git a/api/src/group_permissions_list.ts b/api/src/group_permissions_list.ts index 7ee8c0e49..382684d53 100644 --- a/api/src/group_permissions_list.ts +++ b/api/src/group_permissions_list.ts @@ -80,13 +80,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const groupId = request.query.groupId; if (!isNonemptyString(groupId)) { + const message = "required query parameter `groupId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `groupId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -105,6 +107,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while fetching permissions of group"); reply.status(code).send(body); } }, diff --git a/api/src/httpd/router.ts b/api/src/httpd/router.ts index cac97aa4f..132c7a690 100644 --- a/api/src/httpd/router.ts +++ b/api/src/httpd/router.ts @@ -28,7 +28,7 @@ const handleError = (req, res, err: any) => { switch (err.kind) { case "NotAuthorized": { const message = `User ${err.token.userId} is not authorized.`; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 403, { @@ -68,7 +68,7 @@ const handleError = (req, res, err: any) => { } case "ProjectIdAlreadyExists": { const message = `The project id ${err.projectId} already exists.`; - logger.warn({ error: err }, message); + logger.error({ err }, message); send(res, [ 409, { @@ -80,7 +80,7 @@ const handleError = (req, res, err: any) => { } case "SubprojectIdAlreadyExists": { const message = `The subproject id ${err.subprojectId} already exists.`; - logger.warn({ error: err }, message); + logger.error({ err }, message); send(res, [ 409, { @@ -98,21 +98,21 @@ const handleError = (req, res, err: any) => { } else { message = `Missing keys: ${err.badKeys.join(", ")}`; } - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [400, { apiVersion: "1.0", error: { code: 400, message } }]); break; } case "PreconditionError": { const { message } = err; - logger.warn({ error: err }, message); + logger.error({ err }, message); send(res, [412, { apiVersion: "1.0", error: { code: 412, message } }]); break; } case "AuthenticationError": { const message = "Authentication failed"; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 401, { @@ -125,7 +125,7 @@ const handleError = (req, res, err: any) => { case "NotFound": { const message = "Not found."; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 404, { @@ -138,7 +138,7 @@ const handleError = (req, res, err: any) => { case "FileNotFound": { const message = "File not found."; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 404, { @@ -164,7 +164,7 @@ const handleError = (req, res, err: any) => { case "UnsupportedMediaType": { const message = `Unsupported media type: ${err.contentType}.`; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 415, { @@ -179,7 +179,7 @@ const handleError = (req, res, err: any) => { // handle RPC errors, too: if (err.code === -708) { const message = "Not found."; - logger.debug({ error: err }, message); + logger.error({ err }, message); send(res, [ 404, { diff --git a/api/src/httpd/server.ts b/api/src/httpd/server.ts index d6473c55f..07bd983c4 100644 --- a/api/src/httpd/server.ts +++ b/api/src/httpd/server.ts @@ -5,6 +5,7 @@ import fastifyCors from "fastify-cors"; import helmet from "fastify-helmet"; import { IncomingMessage, Server, ServerResponse } from "http"; import logger from "../lib/logger"; +import { AuthenticatedRequest } from "./lib"; const DEFAULT_API_VERSION = "1.0"; @@ -42,6 +43,7 @@ const addLogging = (server: FastifyInstance) => { id: req.id, url: req.raw.url, params: req.params, + user: (req as AuthenticatedRequest).user, }); done(); }); @@ -128,8 +130,8 @@ export const createBasicApp = ( directives: { defaultSrc: ["'self'"], imgSrc: ["'self'", "data:", "validator.swagger.io"], - scriptSrc: ["'self'"].concat(instance.swaggerCSP.script), - styleSrc: ["'self'", "https:"].concat(instance.swaggerCSP.style), + scriptSrc: ["'self'"].concat((instance as any).swaggerCSP.script), + styleSrc: ["'self'", "https:"].concat((instance as any).swaggerCSP.style), }, }, }; diff --git a/api/src/index.ts b/api/src/index.ts index fc9e58dc6..9a44a644f 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,3 +1,4 @@ +import "module-alias/register"; import { AxiosRequestConfig } from "axios"; import getValidConfig from "./config"; import * as GlobalPermissionsGrantAllAPI from "./global_permissions_grant_all"; diff --git a/api/src/lib/validation.ts b/api/src/lib/validation.ts index aa46d3b73..3f90fbe0c 100644 --- a/api/src/lib/validation.ts +++ b/api/src/lib/validation.ts @@ -11,20 +11,19 @@ export function isNonemptyString(x: any): boolean { export async function isUserOrUndefined(conn: ConnToken, ctx: Ctx, issuer: ServiceUser, input) { if (input === undefined) { return true; - } else { - if (isNonemptyString(input)) { - const user = await UserQuery.getUser(conn, ctx, issuer, input).catch((err) => { - if (err.kind === "NotFound") { - return undefined; - } else { - throw err; - } - }); - return user !== undefined; - } else { - return false; - } } + if (isNonemptyString(input)) { + const user = await UserQuery.getUser(conn, ctx, issuer, input).catch((err) => { + if (err.kind === "NotFound") { + return undefined; + } else { + throw err; + } + }); + return user !== undefined; + } + + return false; } export function findBadKeysInObject( diff --git a/api/src/network/controller/approveNewNodeForExistingOrganization.ts b/api/src/network/controller/approveNewNodeForExistingOrganization.ts index 0642f84ce..f2ea136f9 100644 --- a/api/src/network/controller/approveNewNodeForExistingOrganization.ts +++ b/api/src/network/controller/approveNewNodeForExistingOrganization.ts @@ -1,7 +1,7 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; +import { isNonemptyString, value } from "lib/validation"; import { HttpResponse } from "../../httpd/lib"; -import { Ctx } from "../../lib/ctx"; -import logger from "../../lib/logger"; -import { isNonemptyString, value } from "../../lib/validation"; import { ConnToken } from "../../service"; import { ServiceUser } from "../../service/domain/organization/service_user"; import * as Nodes from "../model/Nodes"; @@ -23,6 +23,7 @@ export async function approveNewNodeForExistingOrganization( // check if node with this address has been registered if (!node) { const message = "No node registered for this address"; + logger.error(message); return [409, { apiVersion: "1.0", error: { code: 409, message } }]; } diff --git a/api/src/network/controller/approveNewOrganization.ts b/api/src/network/controller/approveNewOrganization.ts index f26794375..507911880 100644 --- a/api/src/network/controller/approveNewOrganization.ts +++ b/api/src/network/controller/approveNewOrganization.ts @@ -1,7 +1,7 @@ import { HttpResponse } from "../../httpd/lib"; -import { Ctx } from "../../lib/ctx"; -import logger from "../../lib/logger"; -import { isNonemptyString, value } from "../../lib/validation"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; +import { isNonemptyString, value } from "lib/validation"; import { ConnToken } from "../../service/conn"; import { ServiceUser } from "../../service/domain/organization/service_user"; import * as Nodes from "../model/Nodes"; @@ -27,7 +27,7 @@ export async function approveNewOrganization( if (!futureOrganizationAddress) { const message = `No node registered for organization '${organization}'`; - logger.debug({ error: { organization, multichain, input } }, message); + logger.error({ err: { organization, multichain, input } }, message); throw Error(message); } diff --git a/api/src/network/controller/declineNode.ts b/api/src/network/controller/declineNode.ts index f3b180c31..691bc9f78 100644 --- a/api/src/network/controller/declineNode.ts +++ b/api/src/network/controller/declineNode.ts @@ -1,8 +1,8 @@ import Intent from "../../authz/intents"; import { AddressIsInvalidError, NotFoundError, PreconditionError } from "../../error"; import { HttpResponse } from "../../httpd/lib"; -import logger from "../../lib/logger"; -import { isNonemptyString, value } from "../../lib/validation"; +import logger from "lib/logger"; +import { isNonemptyString, value } from "lib/validation"; import { MultichainClient } from "../../service/Client.h"; import * as Nodes from "../model/Nodes"; diff --git a/api/src/network/controller/list.ts b/api/src/network/controller/list.ts index 863910fd8..0cc82ae2e 100644 --- a/api/src/network/controller/list.ts +++ b/api/src/network/controller/list.ts @@ -2,8 +2,8 @@ import { VError } from "verror"; import { throwIfUnauthorized } from "../../authz"; import Intent from "../../authz/intents"; import { HttpResponse } from "../../httpd/lib"; -import { Ctx } from "../../lib/ctx"; -import logger from "../../lib/logger"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../result"; import { ConnToken } from "../../service"; import { ServiceUser } from "../../service/domain/organization/service_user"; diff --git a/api/src/network/controller/listActive.ts b/api/src/network/controller/listActive.ts index 39b4ef181..fedc87f96 100644 --- a/api/src/network/controller/listActive.ts +++ b/api/src/network/controller/listActive.ts @@ -1,8 +1,9 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import { throwIfUnauthorized } from "../../authz"; import Intent from "../../authz/intents"; import { AuthenticatedRequest, HttpResponse } from "../../httpd/lib"; -import { Ctx } from "../../lib/ctx"; import * as Result from "../../result"; import { ConnToken } from "../../service"; import { ServiceUser } from "../../service/domain/organization/service_user"; @@ -25,6 +26,7 @@ export async function getActiveNodes( issuer, ); if (Result.isErr(globalPermissionsResult)) { + logger.trace("global.grantPermission failed", globalPermissionsResult); throw new VError(globalPermissionsResult, "global.grantPermission failed"); } const globalPermissions = globalPermissionsResult.permissions; diff --git a/api/src/network/controller/logNodes.ts b/api/src/network/controller/logNodes.ts index a4b9065a2..0cd32df9c 100644 --- a/api/src/network/controller/logNodes.ts +++ b/api/src/network/controller/logNodes.ts @@ -1,4 +1,4 @@ -import logger from "../../lib/logger"; +import logger from "lib/logger"; import { MultichainClient } from "../../service/Client.h"; import { WalletAddress } from "../../network/model/Nodes"; import * as NodesLogged from "../../service/domain/network/nodes_logged"; diff --git a/api/src/network/controller/registerNode.ts b/api/src/network/controller/registerNode.ts index 88dcb6c0d..65648b1dd 100644 --- a/api/src/network/controller/registerNode.ts +++ b/api/src/network/controller/registerNode.ts @@ -1,8 +1,8 @@ import Intent from "../../authz/intents"; import { AddressIsInvalidError } from "../../error"; import { HttpResponse } from "../../httpd/lib"; -import logger from "../../lib/logger"; -import { isNonemptyString, value } from "../../lib/validation"; +import logger from "lib/logger"; +import { isNonemptyString, value } from "lib/validation"; import { MultichainClient } from "../../service/Client.h"; import * as Nodes from "../model/Nodes"; diff --git a/api/src/network/controller/vote.ts b/api/src/network/controller/vote.ts index 455bf2896..a40b9e5d1 100644 --- a/api/src/network/controller/vote.ts +++ b/api/src/network/controller/vote.ts @@ -1,10 +1,11 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; +import { isNonemptyString, value } from "lib/validation"; import { VError } from "verror"; import { throwIfUnauthorized } from "../../authz"; import Intent from "../../authz/intents"; +import { config } from "../../config"; import { HttpResponse } from "../../httpd/lib"; -import { Ctx } from "../../lib/ctx"; -import logger from "../../lib/logger"; -import { isNonemptyString, value } from "../../lib/validation"; import * as Result from "../../result"; import { ConnToken } from "../../service"; import { MultichainClient } from "../../service/Client.h"; @@ -12,7 +13,6 @@ import { ServiceUser } from "../../service/domain/organization/service_user"; import * as GlobalPermissionsGet from "../../service/global_permissions_get"; import * as AccessVote from "../model/AccessVote"; import * as Nodes from "../model/Nodes"; -import { config } from "../../config"; export async function voteForNetworkPermission( conn: ConnToken, @@ -30,6 +30,7 @@ export async function voteForNetworkPermission( issuer, ); if (Result.isErr(globalPermissionsResult)) { + logger.trace("global.grantPermission failed", globalPermissionsResult); throw new VError(globalPermissionsResult, "global.grantPermission failed"); } const globalPermissions = globalPermissionsResult.permissions; diff --git a/api/src/network/model/Nodes.ts b/api/src/network/model/Nodes.ts index a7f9b296f..3b69ac03a 100644 --- a/api/src/network/model/Nodes.ts +++ b/api/src/network/model/Nodes.ts @@ -1,5 +1,5 @@ import Intent from "../../authz/intents"; -import logger from "../../lib/logger"; +import logger from "lib/logger"; import * as Result from "../../result"; import { MultichainClient, PeerInfo } from "../../service/Client.h"; import * as NodeRegistered from "../../service/domain/network/node_registered"; diff --git a/api/src/notification_count.ts b/api/src/notification_count.ts index 622da2831..c2dad631a 100644 --- a/api/src/notification_count.ts +++ b/api/src/notification_count.ts @@ -87,6 +87,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while counting notifications"); reply.status(code).send(body); } }); diff --git a/api/src/notification_list.ts b/api/src/notification_list.ts index 83a4db513..e47e207d9 100644 --- a/api/src/notification_list.ts +++ b/api/src/notification_list.ts @@ -314,13 +314,17 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.offset !== undefined) { offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { + const message = "if present, the query parameter `offset` must be an integer"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); + return; } } @@ -330,13 +334,16 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); + return; } } @@ -355,6 +362,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi limit === undefined ? undefined : offsetIndex + limit, ); + request.log.debug("Getting exposed Notifcations"); const exposedNotifications: ExposedNotification[] = []; for (const notification of slice) { const metadata = await getMetadata(ctx, user, notification, service); @@ -381,6 +389,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting Notifications"); reply.status(code).send(body); } }, diff --git a/api/src/notification_mark_read.ts b/api/src/notification_mark_read.ts index 8178aeaa3..99401f785 100644 --- a/api/src/notification_mark_read.ts +++ b/api/src/notification_mark_read.ts @@ -101,6 +101,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to mark notification")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -120,6 +121,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while marking notification as read"); reply.status(code).send(body); } }, diff --git a/api/src/organization/organization.ts b/api/src/organization/organization.ts index 16c1a59a3..9f9260ef1 100644 --- a/api/src/organization/organization.ts +++ b/api/src/organization/organization.ts @@ -67,6 +67,7 @@ export async function getPrivateKey( organization: Organization, organizationVaultSecret: string, ): Promise> { + logger.trace("Fetching organisation address item..."); const privateKeyItemResult = await getPrivateKeyItem(multichain, organization); if (Result.isErr(privateKeyItemResult)) { if (VError.hasCauseWithName(privateKeyItemResult, "NotFound")) { @@ -74,6 +75,8 @@ export async function getPrivateKey( } return new Error("cannot get organization address item"); } + logger.trace("Decrypting organisation address item..."); + const decryptedPrivateKeyResult = SymmetricCrypto.decrypt( organizationVaultSecret, privateKeyItemResult.privateKey, @@ -92,6 +95,7 @@ export async function publishPrivateKey( privateKey: string, organizationVaultSecret: string, ): Promise> { + logger.trace("Publish organisation privat key..."); const privateKeyItemResult = await getPrivateKeyItem(multichain, organization); if (Result.isOk(privateKeyItemResult)) { logger.info("Private Key already published."); @@ -113,6 +117,8 @@ export async function publishPrivateKey( return privateKeyItem; } else { // Non expected error + logger.trace("Publish organisation address ended with an unexpected error!"); + return new VError(privateKeyItemResult, "cannot publish private key"); } } @@ -122,6 +128,8 @@ async function ensureOrganizationAddress( organization: Organization, organizationVaultSecret: string, ): Promise { + logger.trace("Ensuring organisation address..."); + const addressFromStream = await getOrganizationAddressItem(multichain, organization); let organizationAddress: string | undefined; if (addressFromStream) { diff --git a/api/src/project_assign.ts b/api/src/project_assign.ts index 824ee4b9b..fdcada89c 100644 --- a/api/src/project_assign.ts +++ b/api/src/project_assign.ts @@ -102,6 +102,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to assign project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -123,6 +124,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while assigning project"); reply.status(code).send(body); }); }); diff --git a/api/src/project_budget_delete_projected.ts b/api/src/project_budget_delete_projected.ts index c111e7b5d..a58d3df32 100644 --- a/api/src/project_budget_delete_projected.ts +++ b/api/src/project_budget_delete_projected.ts @@ -122,6 +122,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to delete projected budget"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -147,6 +148,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while deleting projected budget"); reply.status(code).send(body); }); }, diff --git a/api/src/project_budget_update_projected.ts b/api/src/project_budget_update_projected.ts index ccab9710b..152c8dd41 100644 --- a/api/src/project_budget_update_projected.ts +++ b/api/src/project_budget_update_projected.ts @@ -134,6 +134,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to update projected budget"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -159,6 +160,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while updating projected budget"); reply.status(code).send(body); }); }, diff --git a/api/src/project_close.ts b/api/src/project_close.ts index f96df0901..0e41b7950 100644 --- a/api/src/project_close.ts +++ b/api/src/project_close.ts @@ -95,6 +95,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to close project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -116,6 +117,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while closing project"); reply.status(code).send(body); }); }); diff --git a/api/src/project_create.ts b/api/src/project_create.ts index 3b9a2dc99..3229590e8 100644 --- a/api/src/project_create.ts +++ b/api/src/project_create.ts @@ -157,6 +157,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to create project")); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid Request body"); return; } @@ -181,6 +182,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi .catch((err) => { const { code, body } = toHttpError(err); reply.status(code).send(body); + request.log.error(err, "Error while creating Project"); }); }); } diff --git a/api/src/project_list.ts b/api/src/project_list.ts index 8a1b89038..fdb72dfcd 100644 --- a/api/src/project_list.ts +++ b/api/src/project_list.ts @@ -167,6 +167,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi throw new VError(result, "project.list failed"); } const projects = result; + request.log.debug("Mapping intents and timestamp of projects"); return projects.map((project) => { return { log: project.log, @@ -198,6 +199,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing projects"); reply.status(code).send(body); }); }); diff --git a/api/src/project_permission_grant.ts b/api/src/project_permission_grant.ts index 7194c781b..0a1c62965 100644 --- a/api/src/project_permission_grant.ts +++ b/api/src/project_permission_grant.ts @@ -119,6 +119,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi new VError(bodyResult, "failed to grant project permission"), ); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid request body"); return; } @@ -139,6 +140,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while granting project permission"); reply.status(code).send(body); }); }, diff --git a/api/src/project_permission_revoke.ts b/api/src/project_permission_revoke.ts index 2df3f9342..f1a2c1623 100644 --- a/api/src/project_permission_revoke.ts +++ b/api/src/project_permission_revoke.ts @@ -118,6 +118,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to revoke project permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -139,6 +140,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while revoking project permission"); reply.status(code).send(body); }); }, diff --git a/api/src/project_permissions_list.ts b/api/src/project_permissions_list.ts index e20fa5fa0..9c5dafbff 100644 --- a/api/src/project_permissions_list.ts +++ b/api/src/project_permissions_list.ts @@ -80,13 +80,17 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } @@ -107,6 +111,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing project permissios"); reply.status(code).send(body); } }, diff --git a/api/src/project_update.ts b/api/src/project_update.ts index e2e7f564e..ccd81fea8 100644 --- a/api/src/project_update.ts +++ b/api/src/project_update.ts @@ -119,6 +119,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to update project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -147,6 +148,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while updating Project"); reply.status(code).send(body); }); }); diff --git a/api/src/project_view_details.ts b/api/src/project_view_details.ts index 4d9b99944..55ab1ceeb 100644 --- a/api/src/project_view_details.ts +++ b/api/src/project_view_details.ts @@ -177,13 +177,17 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } @@ -246,6 +250,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting project details"); reply.status(code).send(body); } }, diff --git a/api/src/project_view_history.ts b/api/src/project_view_history.ts index 6a9adc3d4..26af4ee83 100644 --- a/api/src/project_view_history.ts +++ b/api/src/project_view_history.ts @@ -147,13 +147,18 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; + reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } @@ -162,13 +167,17 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.offset !== undefined) { offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { + const message = "if present, the query parameter `offset` must be an integer"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -178,13 +187,17 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -226,6 +239,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while viewing project history"); reply.status(code).send(body); } }, diff --git a/api/src/project_view_history_v2.ts b/api/src/project_view_history_v2.ts index 73e8e815d..7eb926c57 100644 --- a/api/src/project_view_history_v2.ts +++ b/api/src/project_view_history_v2.ts @@ -1,4 +1,4 @@ -import { FastifyInstance, FastifyReply, RequestGenericInterface } from "fastify"; +import { FastifyInstance, FastifyReply, RequestGenericInterface, FastifyRequest } from "fastify"; import Joi = require("joi"); import VError = require("verror"); @@ -32,25 +32,24 @@ function validateRequestBody(body: any): Result.Type { /** * If no filter option is provided the return value is undefined */ -const createFilter = ( - reply: FastifyReply, - publisher?: Identity, - startAt?: string, - endAt?: string, - eventType?: string, -): History.Filter | undefined => { +const createFilter = (reply: FastifyReply, request: FastifyRequest): History.Filter | undefined => { + const { publisher, startAt, endAt, eventType } = request.query as Querystring; const noFilterSet = !publisher && !startAt && !endAt && !eventType; + if (noFilterSet) return; if (publisher !== undefined) { if (!isNonemptyString(publisher)) { + const message = "if present, the query parameter `publisher` must be non-empty string"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `publisher` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } @@ -58,40 +57,50 @@ const createFilter = ( if (startAt !== undefined) { const startAtDate = new Date(startAt); if (isNaN(startAtDate.getTime())) { + const message = "if present, the query parameter `startAt` must be a valid ISO timestamp"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `startAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (endAt !== undefined) { const endAtDate = new Date(endAt); if (isNaN(endAtDate.getTime())) { + const message = "if present, the query parameter `endAt` must be a valid ISO timestamp"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `endAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (eventType !== undefined) { if (!isNonemptyString(eventType)) { + const message = "if present, the query parameter `eventType` must be non-empty string"; + reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `eventType` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } + return { publisher, startAt, @@ -242,28 +251,36 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; + reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } // Default: last created history event let offset: number = 0; if (request.query.offset !== undefined) { + const message = "if present, the query parameter `offset` must be an integer"; offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -273,24 +290,21 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + + request.log.error({ err: message }, "Invalid request body"); return; } } - const filter = createFilter( - reply, - request.query.publisher, - request.query.startAt, - request.query.endAt, - request.query.eventType, - ); + const filter = createFilter(reply, request); try { // Get all Events in project stream @@ -322,6 +336,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while viewing project history"); reply.status(code).send(body); } }, diff --git a/api/src/provisioning_end.ts b/api/src/provisioning_end.ts index 8f6377d11..4f27086a8 100644 --- a/api/src/provisioning_end.ts +++ b/api/src/provisioning_end.ts @@ -84,6 +84,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to set provisioning.end")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -103,6 +104,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while Ending provisioning"); reply.status(code).send(body); }); }); diff --git a/api/src/provisioning_get.ts b/api/src/provisioning_get.ts index 90210e326..906034b13 100644 --- a/api/src/provisioning_get.ts +++ b/api/src/provisioning_get.ts @@ -83,6 +83,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting provisioning status"); reply.status(code).send(body); }); }); diff --git a/api/src/provisioning_start.ts b/api/src/provisioning_start.ts index 53f74ed71..c9b01bd23 100644 --- a/api/src/provisioning_start.ts +++ b/api/src/provisioning_start.ts @@ -86,6 +86,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to set provisioning.start"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -105,6 +106,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while starting provisioning"); reply.status(code).send(body); }); }); diff --git a/api/src/service/Client.ts b/api/src/service/Client.ts index b94666d8f..0ef5a0f2d 100644 --- a/api/src/service/Client.ts +++ b/api/src/service/Client.ts @@ -202,6 +202,7 @@ export class RpcMultichainClient implements MultichainClient { } public async getValue(streamName: StreamName, key: string): Promise { + logger.trace(`Getting Value "${key}" from stream "${streamName}"`); const result = await this.getValues(streamName, key, 1); if (result.length !== 1) { const message = `Expected a single value, got: ${result || "nothing"}`; @@ -223,6 +224,8 @@ export class RpcMultichainClient implements MultichainClient { key: string, updateCallback: (_: Resource) => Resource, ): Promise { + logger.trace(`Updating Value "${key}" from stream "${streamName}"`); + while (this.hasWriteLock) { await sleep(1); } diff --git a/api/src/service/Client_storage_service.ts b/api/src/service/Client_storage_service.ts index 31e3ae445..2da395791 100644 --- a/api/src/service/Client_storage_service.ts +++ b/api/src/service/Client_storage_service.ts @@ -10,6 +10,7 @@ import { } from "./Client_storage_service.h"; import { config } from "../config"; import { encrypt, decrypt } from "../lib/symmetricCrypto"; +import logger from "lib/logger"; interface UploadRequest { fileName: string; @@ -22,6 +23,7 @@ export default class StorageServiceClient implements StorageServiceClientI { private timeStamp: number = 0; constructor(settings: AxiosRequestConfig) { + logger.debug("Setting up StorageServiceClient"); this.axiosInstance = axios.create(settings); this.axiosInstance.interceptors.request.use((request) => { if (request.url?.includes("/version")) { @@ -67,6 +69,8 @@ export default class StorageServiceClient implements StorageServiceClientI { name: string, data: string, ): Promise> { + logger.debug(`Uploading Object "${name}"`); + let requestData: UploadRequest = { fileName: name, content: data, @@ -84,6 +88,8 @@ export default class StorageServiceClient implements StorageServiceClientI { } public async downloadObject(id: string, secret: string): Promise> { + logger.debug(`Downloading Object with id: "${id}"`); + const url = `/download?docId=${id}`; const axiosConfig = { headers: { @@ -102,6 +108,7 @@ export default class StorageServiceClient implements StorageServiceClientI { }; if (config.encryptionPassword) { + logger.debug("Decrypting file with encryptionPassword"); const fileName = decrypt(config.encryptionPassword, documentObject.fileName); const base64 = decrypt(config.encryptionPassword, documentObject.base64); if (Result.isErr(fileName)) { diff --git a/api/src/service/ProjectEvents.ts b/api/src/service/ProjectEvents.ts index 317bdb91d..3767a5783 100644 --- a/api/src/service/ProjectEvents.ts +++ b/api/src/service/ProjectEvents.ts @@ -366,68 +366,3 @@ function applyRevokePermission(event: Event, project: Project): true | undefined } throwUnsupportedEventVersion(event); } - -export async function issueNotification( - conn: ConnToken, - issuer: Issuer, - message: Event, - recipient: string, -): Promise { - // const notificationId = uuid(); - // // TODO message.key is working for projects - // // TODO but we need to access projectId subprojectid and workflowitemid and build data.resources - // const projectId = message.key; - // const resources: NotificationResourceDescription[] = [ - // { - // id: projectId, - // type: notificationTypeFromIntent(message.intent), - // }, - // ]; - // const intent = "notification.create"; - // const event: Event = { - // key: recipient, - // intent, - // createdBy: issuer.name, - // createdAt: new Date().toISOString(), - // dataVersion: 1, - // data: { - // notificationId, - // resources, - // isRead: false, - // originalEvent: message, - // }, - // }; - // const streamName = "notifications"; - // const publishEvent = () => { - // logger.debug(`Publishing ${intent} to ${streamName}/${recipient}`); - // return conn.multichainClient.getRpcClient().invoke("publish", streamName, recipient, { - // json: event, - // }); - // }; - // return publishEvent().catch(err => { - // if (err.code === -708) { - // logger.debug( - // `The stream ${streamName} does not exist yet. Creating the stream and trying again.`, - // ); - // // The stream does not exist yet. Create the stream and try again: - // return conn.multichainClient - // .getOrCreateStream({ kind: "notifications", name: streamName }) - // .then(() => publishEvent()); - // } else { - // logger.error({ err }, `Publishing ${intent} failed.`); - // throw err; - // } - // }); -} - -function notificationTypeFromIntent(intent: Intent): ResourceType { - if (intent.startsWith("project.") || intent === "global.createProject") { - return "project"; - } else if (intent.startsWith("subproject.") || intent === "project.createSubproject") { - return "subproject"; - } else if (intent.startsWith("workflowitem.") || intent === "subproject.createWorkflowitem") { - return "workflowitem"; - } else { - throw Error(`Unknown ResourceType for intent ${intent}`); - } -} diff --git a/api/src/service/RpcClient.ts b/api/src/service/RpcClient.ts index f2b30bac9..2a9f81e64 100644 --- a/api/src/service/RpcClient.ts +++ b/api/src/service/RpcClient.ts @@ -70,6 +70,7 @@ export class RpcClient { private timeStamp; constructor(settings: ConnectionSettings) { + logger.debug("Setting up RpcClient"); const protocol = `${settings.protocol || "http"}`; const host = settings.host || "localhost"; const port = settings.port || 8570; @@ -198,7 +199,7 @@ export class RpcClient { method, params, }; - logger.trace({ parameters: request }, `Invoking method ${method}`); + logger.trace({ parameters: request }, `Invoking method ${method} on multichain`); return new Promise(async (resolve, reject) => { this.instance diff --git a/api/src/service/SubprojectEvents.ts b/api/src/service/SubprojectEvents.ts deleted file mode 100644 index 50308769b..000000000 --- a/api/src/service/SubprojectEvents.ts +++ /dev/null @@ -1,167 +0,0 @@ -import Intent from "../authz/intents"; -import { People, Permissions } from "../authz/types"; -import deepcopy from "../lib/deepcopy"; -import { inheritDefinedProperties } from "../lib/inheritDefinedProperties"; -import { asMapKey } from "./Client"; -import { ConnToken } from "./conn"; -import { Event, throwUnsupportedEventVersion } from "./event"; - -export * from "./event"; - -interface ProjectedBudget { - organization: string; - value: string; - currencyCode: string; -} - -export interface Subproject { - id: string; - creationUnixTs: string; - status: "open" | "closed"; - displayName: string; - description: string; - assignee?: string; - currency: string; - projectedBudgets: ProjectedBudget[]; - permissions: Permissions; - log: HistoryEvent[]; -} - -interface HistoryEvent { - key: string; // the resource ID (same for all events that relate to the same resource) - intent: Intent; - createdBy: string; - createdAt: string; - dataVersion: number; // integer - data: any; - snapshot: { - displayName: string; - }; -} - -export async function getSubprojectList(conn: ConnToken, projectId: string): Promise { - const streamItems = await conn.multichainClient.v2_readStreamItems(projectId, "subprojects"); - const subprojectsByKey = new Map(); - - for (const item of streamItems) { - const key = asMapKey(item); - const event = item.data.json as Event; - - let subproject = subprojectsByKey.get(key); - if (subproject === undefined) { - subproject = handleCreate(event); - if (subproject === undefined) { - const message = "Failed to initialize resource"; - throw Error(`${message}: ${JSON.stringify(event)}.`); - } - subprojectsByKey.set(key, subproject); - } else { - const hasProcessedEvent = - applyUpdate(event, subproject) || - applyAssign(event, subproject) || - applyClose(event, subproject) || - applyGrantPermission(event, subproject) || - applyRevokePermission(event, subproject); - if (!hasProcessedEvent) { - throw Error(`Unexpected event: ${JSON.stringify(event)}.`); - } - } - subproject.log.push({ - ...event, - snapshot: { displayName: deepcopy(subproject.displayName) }, - }); - } - - return [...subprojectsByKey.values()]; -} - -function handleCreate(event: Event): Subproject | undefined { - if (event.intent !== "project.createSubproject") return undefined; - switch (event.dataVersion) { - case 1: { - const { subproject, permissions } = event.data; - return { - ...deepcopy(subproject), - permissions: deepcopy(permissions), - log: [], - }; - } - } - throwUnsupportedEventVersion(event); -} - -function applyUpdate(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.update") return; - switch (event.dataVersion) { - case 1: { - inheritDefinedProperties(subproject, event.data); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function applyAssign(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.assign") return; - switch (event.dataVersion) { - case 1: { - const { identity } = event.data; - subproject.assignee = identity; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function applyClose(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.close") return; - switch (event.dataVersion) { - case 1: { - subproject.status = "closed"; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function applyGrantPermission(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.intent.grantPermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - grantSubprojectPermission(subproject, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function applyRevokePermission(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.intent.revokePermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - revokeSubprojectPermission(subproject, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function grantSubprojectPermission(subproject: Subproject, identity: string, intent: Intent) { - const permissionsForIntent: People = subproject.permissions[intent] || []; - if (!permissionsForIntent.includes(identity)) { - permissionsForIntent.push(identity); - } - subproject.permissions[intent] = permissionsForIntent; -} - -function revokeSubprojectPermission(subproject: Subproject, identity: string, intent: Intent) { - const permissionsForIntent: People = subproject.permissions[intent] || []; - const userIndex = permissionsForIntent.indexOf(identity); - if (userIndex !== -1) { - // Remove the user from the array: - permissionsForIntent.splice(userIndex, 1); - subproject.permissions[intent] = permissionsForIntent; - } -} diff --git a/api/src/service/User.ts b/api/src/service/User.ts deleted file mode 100644 index 9c2d594d4..000000000 --- a/api/src/service/User.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface User { - id: string; - groups: string[]; -} - -export function userIdentities({ id, groups }: User): string[] { - return [id].concat(groups); -} diff --git a/api/src/service/Workflowitem.ts b/api/src/service/Workflowitem.ts deleted file mode 100644 index cf0ca2834..000000000 --- a/api/src/service/Workflowitem.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { Event, throwUnsupportedEventVersion } from "."; -import Intent from "../authz/intents"; -import { People, Permissions } from "../authz/types"; -import deepcopy from "../lib/deepcopy"; -import { inheritDefinedProperties } from "../lib/inheritDefinedProperties"; -import Type from "./domain/workflowitem_types/types"; - -const maxItemCount: number = 0x7fffffff; - -export interface Document { - id: string; - hash: string; - fileName?: string; -} - -interface HistoryEvent { - key: string; // the resource ID (same for all events that relate to the same resource) - intent: Intent; - createdBy: string; - createdAt: string; - dataVersion: number; // integer - data: any; - snapshot: { - displayName: string; - amount: string; - currency: string; - amountType: string; - }; -} - -export interface Update { - displayName?: string; - amount?: string; - currency?: string; - amountType?: "N/A" | "disbursed" | "allocated"; - description?: string; - documents?: Document[]; - exchangeRate?: string; - billingDate?: string; - dueDate?: string; -} - -export interface Workflowitem { - id: string; - creationUnixTs: string; - displayName: string; - exchangeRate?: string; - billingDate?: string; - dueDate?: string; - amount?: string; - currency?: string; - amountType: "N/A" | "disbursed" | "allocated"; - description: string; - status: "open" | "closed"; - assignee?: string; - documents?: Document[]; - permissions: Permissions; - log: HistoryEvent[]; - workflowitemType?: Type; -} - -export type ScrubbedWorkflowitem = Workflowitem | RedactedWorkflowitem; - -export interface RedactedWorkflowitem { - id: string; - creationUnixTs: string; - displayName: null; - exchangeRate: null; - billingDate?: null; - dueDate?: null; - amount?: null; - currency?: null; - amountType: null; - description: null; - status: "open" | "closed"; - assignee?: null; - documents?: null; - permissions: null; - log: null; - workflowitemType?: Type; -} - -export interface User { - id: string; - groups: string[]; -} - -export function userIdentities({ id, groups }: User): string[] { - return [id].concat(groups); -} - -export function applyUpdate(event: Event, workflowitem: Workflowitem): true | undefined { - if (event.intent !== "workflowitem.update") return; - switch (event.dataVersion) { - case 1: { - if (event.data.documents) { - const currentDocs = workflowitem.documents || []; - const currentIds = currentDocs.map((doc) => doc.id); - const newDocs = event.data.documents.filter((doc) => !currentIds.includes(doc.id)); - if (workflowitem.documents) { - workflowitem.documents.push(...newDocs); - } else { - workflowitem.documents = newDocs; - } - delete event.data.documents; - } - const update: Update = event.data; - - inheritDefinedProperties(workflowitem, update); - // In case the update has set the amountType to N/A, we don't want to retain the - // amount and currency fields: - if (workflowitem.amountType === "N/A") { - delete workflowitem.amount; - delete workflowitem.currency; - } - - return true; - } - } - throwUnsupportedEventVersion(event); -} - -export function applyAssign(event: Event, workflowitem: Workflowitem): true | undefined { - if (event.intent !== "workflowitem.assign") return; - switch (event.dataVersion) { - case 1: { - const { identity } = event.data; - workflowitem.assignee = identity; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -export function applyClose(event: Event, workflowitem: Workflowitem): true | undefined { - if (event.intent !== "workflowitem.close") return; - switch (event.dataVersion) { - case 1: { - workflowitem.status = "closed"; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -export function applyGrantPermission(event: Event, workflowitem: Workflowitem): true | undefined { - const permissions = workflowitem.permissions; - if (event.intent !== "workflowitem.intent.grantPermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - const permissionsForIntent: People = permissions[intent] || []; - if (!permissionsForIntent.includes(identity)) { - permissionsForIntent.push(identity); - } - permissions[intent] = permissionsForIntent; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -export function applyRevokePermission(event: Event, workflowitem: Workflowitem): true | undefined { - const permissions = workflowitem.permissions; - if (event.intent !== "workflowitem.intent.revokePermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - const permissionsForIntent: People = permissions[intent] || []; - const userIndex = permissionsForIntent.indexOf(identity); - if (userIndex !== -1) { - // Remove the user from the array: - permissionsForIntent.splice(userIndex, 1); - permissions[intent] = permissionsForIntent; - } - return true; - } - } - throwUnsupportedEventVersion(event); -} - -export function handleCreate(event): Workflowitem | undefined { - if (event.type !== "workflowitem_created") return undefined; - const { workflowitem } = event; - const values = { - ...deepcopy(workflowitem), - permissions: deepcopy(workflowitem.permissions), - log: [], - }; - return values as Workflowitem; -} diff --git a/api/src/service/cache.ts b/api/src/service/cache.ts index b385a5b6b..445114442 100644 --- a/api/src/service/cache.ts +++ b/api/src/service/cache.ts @@ -1,17 +1,6 @@ import Intent from "../authz/intents"; -import deepcopy from "../lib/deepcopy"; -import logger from "../lib/logger"; -import { MultichainClient, Stream } from "./Client.h"; -import { ConnToken } from "./conn"; -import * as Eventsourcing from "./eventsourcing"; -import { Item } from "./liststreamitems"; -import * as ProjectEvents from "./ProjectEvents"; - -// Currently the project-stream cache doesn't quite work: -// Projects aren't updated as long as the items count in the project-stream list doesn't -// change, so fixing the latter also fixes the former. -// Set to zero to disable this: -const PROJECT_STREAMS_MILLISECONDS_TO_LIVE = 0; +import { Stream } from "./Client.h"; +import Type from "./domain/workflowitem_types/types"; type StreamName = string; type StreamCursor = { txid: string; index: number }; @@ -24,7 +13,7 @@ export type Cache = { // Cached project streams, to be invalidated regularly and after project.create: projectStreams?: Stream[]; // The cached content: - projects: Map; + projects: Map; }; export function initCache(): Cache { @@ -36,254 +25,67 @@ export function initCache(): Cache { }; } -export async function tellCacheWhatHappened(cache: Cache, whatHappened: Intent): Promise { - if (whatHappened === "global.createProject" && PROJECT_STREAMS_MILLISECONDS_TO_LIVE > 0) { - logger.debug("global.createProject => invalidating project-stream cache.."); - await invalidateProjectStreamsCache(cache); - } +interface Project { + id: string; + creationUnixTs: string; + status: "open" | "closed"; + displayName: string; + assignee?: string; + description: string; + projectedBudgets: ProjectedBudget[]; + thumbnail: string; + permissions: Permissions; + log: HistoryEvent[]; + subprojects: Map; } -async function invalidateProjectStreamsCache(cache: Cache): Promise { - if (PROJECT_STREAMS_MILLISECONDS_TO_LIVE > 0) { - await grabWriteLock(cache); - cache.projectStreams = undefined; - logger.debug("project-stream cache invalidated"); - releaseWriteLock(cache); - } +interface Subproject { + id: string; + creationUnixTs: string; + status: "open" | "closed"; + displayName: string; + description: string; + assignee?: string; + currency: string; + projectedBudgets: ProjectedBudget[]; + permissions: Permissions; + log: HistoryEvent[]; + workflowitems: Map; } -async function grabWriteLock(cache: Cache) { - while (cache.isWriteLocked) { - await new Promise((res) => setTimeout(res, 1)); - } - cache.isWriteLocked = true; +interface Workflowitem { + id: string; + creationUnixTs: string; + displayName: string; + exchangeRate?: string; + billingDate?: string; + dueDate?: string; + amount?: string; + currency?: string; + amountType: "N/A" | "disbursed" | "allocated"; + description: string; + status: "open" | "closed"; + assignee?: string; + documents?: Document[]; + permissions: Permissions; + log: HistoryEvent[]; + workflowitemType?: Type; } -function releaseWriteLock(cache: Cache) { - cache.isWriteLocked = false; +interface ProjectedBudget { + organization: string; + value: string; + currencyCode: string; } -function dropSubprojects(x: Eventsourcing.Project): ProjectEvents.Project { - return { - id: x.id, - creationUnixTs: x.creationUnixTs, - status: x.status, - displayName: x.displayName, - assignee: x.assignee, - description: x.description, - projectedBudgets: x.projectedBudgets, - thumbnail: x.thumbnail, - permissions: deepcopy(x.permissions), - log: deepcopy(x.log), +interface HistoryEvent { + key: string; // the resource ID (same for all events that relate to the same resource) + intent: Intent; + createdBy: string; + createdAt: string; + dataVersion: number; // integer + data: any; + snapshot: { + displayName: string; }; } - -export async function getAndCacheProject( - conn: ConnToken, - projectId: string, -): Promise { - const { cache } = conn; - - try { - // Make sure we're the only thread-of-execution that updates the cache: - await grabWriteLock(cache); - - await updateCache(conn); - - // Return cached: - const project = cache.projects.get(projectId); - return project !== undefined - ? dropSubprojects(project) - : Promise.reject(Error(`Project ${projectId} not found`)); - } finally { - releaseWriteLock(cache); - } -} - -export async function getAndCacheProjectList(conn: ConnToken): Promise { - const { cache } = conn; - - try { - // Make sure we're the only thread-of-execution that updates the cache: - await grabWriteLock(cache); - - await updateCache(conn); - - // Return cached: - return [...cache.projects.values()].map(dropSubprojects); - } finally { - releaseWriteLock(cache); - } -} - -async function findStartIndex( - multichainClient: MultichainClient, - streamName: string, - cursor?: StreamCursor, -): Promise { - if (cursor === undefined) return 0; - const rpcClient = multichainClient.getRpcClient(); - const verbose = false; - const count = 1; - const start = cursor.index; - const items: Item[] = await rpcClient.invoke( - "liststreamitems", - streamName, - verbose, - count, - start, - ); - if (items.length !== 1) return 0; - const item = items[0]; - return item.txid === cursor.txid ? cursor.index + 1 : 0; -} - -async function fetchItems( - multichainClient: MultichainClient, - streamName: string, - start: number, - count: number, -): Promise { - const rpcClient = multichainClient.getRpcClient(); - const verbose = false; - const items: Item[] = await rpcClient.invoke( - "liststreamitems", - streamName, - verbose, - count, - start, - ); - return items; -} - -async function getProjectStreams(conn: ConnToken, projectId?: string): Promise { - const { cache, multichainClient } = conn; - const isStreamCacheEnabled = PROJECT_STREAMS_MILLISECONDS_TO_LIVE > 0; - if (projectId !== undefined) { - if (isStreamCacheEnabled && cache.projectStreams !== undefined) { - return cache.projectStreams.filter((x) => x.name === projectId); - } else { - // liststreams also gives us streams from the mempool (with confirmed=0). - return await multichainClient.streams(projectId); - } - } else { - // projectId is not set, so we need the list of _all_ projects. - - if (isStreamCacheEnabled && cache.projectStreams !== undefined) { - return cache.projectStreams; - } else { - // liststreams also gives us streams from the mempool (with confirmed=0). - const streams = await conn.multichainClient.streams(); - - // We only cache projects, so we reject all other streams: - cache.projectStreams = streams.filter((x) => x.details.kind === "project"); - - // Invalidate the cached data eventually: - setTimeout(() => invalidateProjectStreamsCache(cache), PROJECT_STREAMS_MILLISECONDS_TO_LIVE); - - return cache.projectStreams; - } - } -} - -async function updateCache(conn: ConnToken, maybeOnlySpecificProject?: string): Promise { - // Let's gather some statistics: - let nUpdatedProjects = 0; - let nRebuiltProjects = 0; - const startTime = process.hrtime(); - - const { cache } = conn; - - const projectStreams = await getProjectStreams(conn, maybeOnlySpecificProject); - - for (const { name: streamName, items: nStreamItems } of projectStreams) { - if (nStreamItems === 0) { - if (logger.levelVal >= logger.levels.values.debug) { - const stream = projectStreams.find((x) => x.name === streamName); - logger.debug({ stream }, `Found empty stream ${streamName}`); - } - continue; - } - - const cursor = cache.streamState.get(streamName); - - // If the number of items hasn't changed, we don't need to check for changes. Even if - // the last item has changed in the meantime, we'll pick up the change along with the - // next. And since MultiChain likely merges events when mining blocks rather than - // replacing them, a changed item without a following new item is quite unlikely. - if (cursor !== undefined && cursor.index === nStreamItems - 1) { - logger.trace({ streamName, nStreamItems, cursor }, `Cache hit for stream ${streamName}`); - continue; - } - - const newItems: Item[] = []; - - let first = await findStartIndex(conn.multichainClient, streamName, cursor); - const isRebuild = cursor === undefined || first !== cursor.index + 1; - logger.trace( - { streamName, nStreamItems, cursor }, - `Cache miss for stream ${streamName} (full rebuild: ${isRebuild ? "yes" : "no"})`, - ); - const batchSize = 100; - let batch: Item[] = []; - while ( - (batch = await fetchItems(conn.multichainClient, streamName, first, batchSize)).length > 0 - ) { - logger.trace({ batch }); - newItems.push(...batch); - first += batch.length; - } - - // It would be nice to have a `panic!` macro, but whatever: - if (isRebuild && (!newItems.length || !first)) { - logger.fatal( - { streamName, nStreamItems, cursor, first, isRebuild, newItems }, - "Found a bug!", - ); - process.exit(1); - } - logger.trace({ streamName, nStreamItems, cursor, first, isRebuild, newItems }); - - let cursorToLastItem: StreamCursor | undefined = cursor; - // If there are new items, we update the cursor to point to the latest one: - const lastIndex = first - 1; - const lastTxid = newItems[newItems.length - 1].txid; - cursorToLastItem = { index: lastIndex, txid: lastTxid }; - - const projectId = streamName; - let cachedProject: Eventsourcing.Project | undefined; - if (isRebuild) { - logger.debug( - { projectId, cursor, cursorToLastItem, nItems: newItems.length }, - `Rebuilding cache for project ${projectId}`, - ); - } else { - cachedProject = cache.projects.get(projectId); - } - - const updatedProject = await Eventsourcing.applyStreamItems(newItems, cachedProject); - if (updatedProject !== undefined) { - cache.projects.set(projectId, updatedProject); - } - - if (cursorToLastItem !== undefined) { - cache.streamState.set(streamName, cursorToLastItem); - } - - if (isRebuild) { - ++nRebuiltProjects; - } else { - ++nUpdatedProjects; - } - } - - if (logger.levelVal >= logger.levels.values.debug) { - // Returns [seconds, nanoseconds]: - const hrtimeDiff = process.hrtime(startTime); - const elapsedMilliseconds = (hrtimeDiff[0] * 1e9 + hrtimeDiff[1]) / 1e6; - logger.debug( - cache.streamState, - `Stream cache updated in ${elapsedMilliseconds} ms: ${projectStreams.length} projects (${nUpdatedProjects} updated, ${nRebuiltProjects} rebuilt)`, - ); - } -} diff --git a/api/src/service/cache2.ts b/api/src/service/cache2.ts index 27d504f4c..e1e1ffd29 100644 --- a/api/src/service/cache2.ts +++ b/api/src/service/cache2.ts @@ -61,7 +61,6 @@ import * as StorageServiceUrlUpdated from "./domain/document/storage_service_url import { Item } from "./liststreamitems"; import * as ProvisioningStarted from "./domain/system_information/provisioning_started"; import * as ProvisioningEnded from "./domain/system_information/provisioning_ended"; -import { RpcClient } from "./RpcClient"; const STREAM_BLACKLIST = [ // The organization address is written directly (i.e., not as event): @@ -147,24 +146,30 @@ interface CacheInstance { export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { return { getGlobalEvents: (): BusinessEvent[] => { + logger.trace("Getting global Events from cache"); return cache.eventsByStream.get("global") || []; }, //system_information getSystemEvents: (): BusinessEvent[] => { + logger.trace("Getting system Events from cache"); return cache.eventsByStream.get("system_information") || []; }, getUserEvents: (_userId?: string): BusinessEvent[] => { + logger.trace("Getting user events from cache"); // userId currently not leveraged return cache.eventsByStream.get("users") || []; }, getGroupEvents: (_groupId?: string): BusinessEvent[] => { + logger.trace("Getting group events from cache"); // groupId currently not leveraged return cache.eventsByStream.get("groups") || []; }, getNotificationEvents: (userId: string): Result.Type => { + logger.trace("Getting Notification Events from cache"); + const userFilter = (event) => { if (!event.type.startsWith("notification_")) { logger.debug(`Unexpected event type in "notifications" stream: ${event.type}`); @@ -185,9 +190,11 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { }, getPublicKeyEvents: (): Result.Type => { + logger.trace("Getting public key events from cache"); return cache.eventsByStream.get("public_keys") || []; }, getOffchainDocumentsEvents: (): Result.Type => { + logger.trace("Getting offchain document events from cache"); const documentFilter = (event) => { switch (event.type) { case "workflowitem_document_uploaded": @@ -199,6 +206,7 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { return cache.eventsByStream.get("offchain_documents") || [].filter(documentFilter); }, getDocumentUploadedEvents: (): Result.Type => { + logger.trace("Getting document uploaded events"); const documentFilter = (event) => { switch (event.type) { case "document_uploaded": @@ -212,6 +220,7 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { return cache.eventsByStream.get("offchain_documents") || [].filter(documentFilter); }, getStorageServiceUrlPublishedEvents: (): Result.Type => { + logger.trace("Getting storageserviceurl-published events from cache"); const storageServiceUrlFilter = (event) => { switch (event.type) { case "storage_service_url_published": @@ -223,6 +232,7 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { return cache.eventsByStream.get("offchain_documents") || [].filter(storageServiceUrlFilter); }, getSecretPublishedEvents: (): Result.Type => { + logger.trace("Getting Secret published events from cache"); const secretPhublishedFilter = (event) => { switch (event.type) { case "secret_published": @@ -235,10 +245,12 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { }, getProjects: async (): Promise => { + logger.trace("Getting projects from cache"); return [...cache.cachedProjects.values()]; }, getProject: async (projectId: string): Promise> => { + logger.trace(`Getting Project with id "${projectId}" from cache`); const projects = [...cache.cachedProjects.values()]; const project = projects.find((x) => x.id === projectId); if (project === undefined) { @@ -248,6 +260,8 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { }, getSubprojects: async (projectId: string): Promise> => { + logger.trace("Getting subprojects from cache"); + // Look up subproject ids const subprojectIDs = cache.cachedSubprojectLookup.get(projectId); if (subprojectIDs === undefined) { @@ -271,6 +285,9 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { _projectId: string, subprojectId: string, ): Result.Type => { + logger.trace( + `Getting Subproject from project ${_projectId} with id "${subprojectId}" from cache`, + ); const subproject = cache.cachedSubprojects.get(subprojectId); if (subproject === undefined) { return new NotFound(ctx, "subproject", subprojectId); @@ -282,6 +299,7 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { _projectId: string, subprojectId: string, ): Promise> => { + logger.trace("Getting workflowitems from cache"); const workflowitemIDs = cache.cachedWorkflowitemLookup.get(subprojectId); const workflowitems: Workflowitem.Workflowitem[] = []; if (workflowitemIDs === undefined) { @@ -305,6 +323,10 @@ export function getCacheInstance(ctx: Ctx, cache: Cache2): CacheInstance { _subprojectId: string, workflowitemId: string, ): Promise> => { + logger.trace( + `Getting Workflowitem from project ${_projectId} and ${_subprojectId} with id "${workflowitemId}" from cache`, + ); + const workflowitem = cache.cachedWorkflowItems.get(workflowitemId); if (workflowitem === undefined) { return new NotFound(ctx, "workflowitem", workflowitemId); @@ -515,7 +537,7 @@ function addEventsToCache(cache: Cache2, streamName: string, newEvents: Business export function updateAggregates(ctx: Ctx, cache: Cache2, newEvents: BusinessEvent[]) { const { projects, errors: pErrors = [] } = sourceProjects(ctx, newEvents, cache.cachedProjects); - if (!isEmpty(pErrors)) logger.debug("sourceProject caused error: ", pErrors); + if (!isEmpty(pErrors)) logger.error({ err: pErrors }, "sourceProject caused error"); for (const project of projects) { cache.cachedProjects.set(project.id, project); @@ -526,7 +548,7 @@ export function updateAggregates(ctx: Ctx, cache: Cache2, newEvents: BusinessEve newEvents, cache.cachedSubprojects, ); - if (!isEmpty(spErrors)) logger.debug("sourceSubproject caused error: ", spErrors); + if (!isEmpty(spErrors)) logger.error({ err: spErrors }, "sourceSubproject caused error: "); for (const subproject of subprojects) { cache.cachedSubprojects.set(subproject.id, subproject); @@ -544,7 +566,7 @@ export function updateAggregates(ctx: Ctx, cache: Cache2, newEvents: BusinessEve newEvents, cache.cachedWorkflowItems, ); - if (!isEmpty(wErrors)) logger.debug("sourceWorkflowitems caused error: ", wErrors); + if (!isEmpty(wErrors)) logger.error({ err: wErrors }, "sourceWorkflowitems caused error: "); for (const workflowitem of workflowitems) { cache.cachedWorkflowItems.set(workflowitem.id, workflowitem); @@ -616,13 +638,11 @@ export function parseBusinessEvents( .map((item) => { const event = item.data.json; if (!event) { - logger.info( - `cache2: ignoring event no item.data.json property found ${JSON.stringify(item)}`, - ); + logger.warn(`ignoring event no item.data.json property found ${JSON.stringify(item)}`); return; } if (event.intent) { - logger.debug(`cache2: ignoring event of intent ${event.intent}`); + logger.warn(`ignoring event of intent ${event.intent}`); return; } const parser = EVENT_PARSER_MAP[event.type]; diff --git a/api/src/service/document_share.ts b/api/src/service/document_share.ts index 04a7acfd7..47098b0f9 100644 --- a/api/src/service/document_share.ts +++ b/api/src/service/document_share.ts @@ -12,7 +12,6 @@ import * as SecretGet from "./domain/document/secret_get"; import { ServiceUser } from "./domain/organization/service_user"; import * as PublicKeyGet from "./public_key_get"; import { store } from "./store"; -import * as UserQuery from "./user_query"; export async function documentShare( conn: ConnToken, diff --git a/api/src/service/document_upload.ts b/api/src/service/document_upload.ts index 4b0e609af..9498aae39 100644 --- a/api/src/service/document_upload.ts +++ b/api/src/service/document_upload.ts @@ -13,6 +13,7 @@ import { ServiceUser } from "./domain/organization/service_user"; import * as PublicKeyGet from "./public_key_get"; import * as UserQuery from "./user_query"; import { store } from "./store"; +import logger from "lib/logger"; export async function documentUpload( conn: ConnToken, @@ -21,6 +22,7 @@ export async function documentUpload( serviceUser: ServiceUser, requestData: DocumentUpload.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Uploading document"); const uploadedDocumentResult = await Cache.withCache(conn, ctx, async (cache) => { return DocumentUpload.uploadDocument(ctx, serviceUser, requestData, { getAllDocuments: async () => { diff --git a/api/src/service/document_validation.ts b/api/src/service/document_validation.ts index 77c85767b..6ce114ac1 100644 --- a/api/src/service/document_validation.ts +++ b/api/src/service/document_validation.ts @@ -11,6 +11,7 @@ import * as Subproject from "./domain/workflow/subproject"; import * as Workflowitem from "./domain/workflow/workflowitem"; import * as GroupQuery from "./group_query"; import { store } from "./store"; +import logger from "lib/logger"; /** * Returns true if the given hash matches the given document. @@ -29,6 +30,8 @@ export async function isSameDocument( subprojectId: Subproject.Id, workflowitemId: Workflowitem.Id, ): Promise> { + logger.debug(`Validating wether passed hash matches document "${documentId}"`); + let isDocumentValid: boolean = false; try { const hash = crypto.createHash("sha256"); diff --git a/api/src/service/domain/document/decryptprivkey.js b/api/src/service/domain/document/decryptprivkey.js index 2e7a5f7c9..338d9f2f7 100644 --- a/api/src/service/domain/document/decryptprivkey.js +++ b/api/src/service/domain/document/decryptprivkey.js @@ -16,6 +16,8 @@ try { } function encrypt(organizationSecret, plaintext) { + logger.trace("Encrypting organization secret ..."); + const plaintextBuffer = Buffer.from(plaintext); // The nonce/salt will be prepended to the ciphertext: @@ -44,6 +46,8 @@ function encrypt(organizationSecret, plaintext) { function decrypt(organizationSecret, hexEncodedCiphertext) { // The nonce/salt is prepended to the actual ciphertext: + logger.trace("Decreypting organization secret ..."); + const dataBuffer = Buffer.from(hexEncodedCiphertext, "hex"); const nonceBuffer = dataBuffer.slice(0, sodium.crypto_secretbox_NONCEBYTES); const cipherBuffer = dataBuffer.slice(sodium.crypto_secretbox_NONCEBYTES); @@ -60,6 +64,7 @@ function decrypt(organizationSecret, hexEncodedCiphertext) { keyBuffer, ) ) { + logger.trace("Encrypting organization secret failed!"); return new Error("decryption failed"); } diff --git a/api/src/service/domain/document/document.ts b/api/src/service/domain/document/document.ts index 9a34d4fdf..6a30cb327 100644 --- a/api/src/service/domain/document/document.ts +++ b/api/src/service/domain/document/document.ts @@ -3,6 +3,7 @@ import Joi = require("joi"); import uuid = require("uuid"); import * as Result from "../../../result"; import VError = require("verror"); +import logger from "lib/logger"; export interface StoredDocument { id: string; @@ -54,6 +55,7 @@ export const uploadedDocumentSchema = Joi.object({ export async function hashDocument( document: UploadedDocument, ): Promise> { + logger.trace({ document: document.fileName }, "Hashing document"); return hashBase64String(document.base64).then((hashValue) => ({ id: document.id, hash: hashValue, diff --git a/api/src/service/domain/document/document_download.spec.ts b/api/src/service/domain/document/document_download.spec.ts index 6eb5e8727..56aab8ae2 100644 --- a/api/src/service/domain/document/document_download.spec.ts +++ b/api/src/service/domain/document/document_download.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { Workflowitem } from "../workflow/workflowitem"; diff --git a/api/src/service/domain/document/document_eventsourcing.spec.ts b/api/src/service/domain/document/document_eventsourcing.spec.ts index 382ea2815..c8b437429 100644 --- a/api/src/service/domain/document/document_eventsourcing.spec.ts +++ b/api/src/service/domain/document/document_eventsourcing.spec.ts @@ -3,7 +3,7 @@ import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; import { UploadedDocument } from "./document"; import { sourceDocuments, sourceOffchainDocuments, sourceSecrets } from "./document_eventsourcing"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import * as DocumentUploaded from "./document_uploaded"; diff --git a/api/src/service/domain/document/document_eventsourcing.ts b/api/src/service/domain/document/document_eventsourcing.ts index 253e128a1..dd0e2c9c8 100644 --- a/api/src/service/domain/document/document_eventsourcing.ts +++ b/api/src/service/domain/document/document_eventsourcing.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -7,6 +7,7 @@ import * as WorkflowitemDocumentUploaded from "./workflowitem_document_uploaded" import * as DocumentShared from "./document_shared"; import { UploadedDocument } from "./document"; import { applyStorageServiceUrls as applyStorageServiceUrl } from "./storage_service_url_eventsourcing"; +import logger from "lib/logger"; export function sourceDocuments( ctx: Ctx, @@ -19,12 +20,15 @@ export function sourceDocuments( for (const event of events) { apply(ctx, documents, urls, event, errors); } + for (const doc of documents) { const url = urls.get(doc.organization); + if (url) { doc.organizationUrl = url; } } + return { documents, errors }; } @@ -35,6 +39,7 @@ function apply( event: BusinessEvent, errors: EventSourcingError[], ) { + logger.trace({ event }, "Applying document event"); if (event.type === "document_uploaded") { newDocumentFromEvent(ctx, documents, event, errors); } else if (event.type === "storage_service_url_published") { @@ -60,6 +65,7 @@ function newDocumentFromEvent( errors.push(new EventSourcingError({ ctx, event }, result)); return; } + documents.push(result); } @@ -78,6 +84,7 @@ export function sourceOffchainDocuments( return { documents, errors }; } + function newOffchainDocumentFromEvent( ctx: Ctx, documents: UploadedDocument[], @@ -101,6 +108,8 @@ export function sourceSecrets( const errors: EventSourcingError[] = []; for (const event of events) { + logger.trace({ event }, "Creating new secret from event"); + if (event.type === "secret_published") { newSecretFromEvent(ctx, secrets, event, errors); } diff --git a/api/src/service/domain/document/document_get.spec.ts b/api/src/service/domain/document/document_get.spec.ts index d41164f36..34eed2400 100644 --- a/api/src/service/domain/document/document_get.spec.ts +++ b/api/src/service/domain/document/document_get.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/document/document_get.ts b/api/src/service/domain/document/document_get.ts index 9c2bba83f..9b769127a 100644 --- a/api/src/service/domain/document/document_get.ts +++ b/api/src/service/domain/document/document_get.ts @@ -1,10 +1,11 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { GenericDocument, UploadedDocument } from "./document"; import { sourceDocuments, sourceOffchainDocuments } from "./document_eventsourcing"; import * as DocumentUploaded from "./document_uploaded"; +import logger from "lib/logger"; interface Repository { getDocumentsEvents(): Promise>; @@ -15,14 +16,18 @@ export async function getAllDocuments( ctx: Ctx, repository: Repository, ): Promise> { + logger.trace("Getting all documents from storage"); const documentsFromStorage = await getAllDocumentInfos(ctx, repository); + if (Result.isErr(documentsFromStorage)) { return new VError(documentsFromStorage, "get all documents from storage failed"); } + const offchainDocuments = await getAllDocumentsFromOffchainStorage(ctx, repository); if (Result.isErr(offchainDocuments)) { return new VError(offchainDocuments, "get all offchain documents failed"); } + return [...documentsFromStorage, ...offchainDocuments]; } @@ -30,10 +35,13 @@ export async function getAllDocumentInfos( ctx: Ctx, repository: Repository, ): Promise> { + logger.trace("Getting all document infos"); const documentEvents = await repository.getDocumentsEvents(); + if (Result.isErr(documentEvents)) { return new VError(documentEvents, "fetch storage documents events failed"); } + const { documents } = sourceDocuments(ctx, documentEvents); return documents; } @@ -43,10 +51,13 @@ export async function getDocumentInfo( docId: string, repository: Repository, ): Promise> { + logger.trace({ docId }, "Getting infos of document by id"); const documentInfos = await getAllDocumentInfos(ctx, repository); + if (Result.isErr(documentInfos)) { return new VError(documentInfos, "get all documents from storage failed"); } + const document = documentInfos.find((doc) => doc.id === docId); return document; } @@ -55,11 +66,13 @@ export async function getAllDocumentsFromOffchainStorage( ctx: Ctx, repository: Repository, ): Promise> { + logger.trace("Getting all documents from offchain storage"); const documentEvents = await repository.getOffchainDocumentsEvents(); if (Result.isErr(documentEvents)) { return new VError(documentEvents, "fetch offchain_documents events failed"); } + const { documents } = sourceOffchainDocuments(ctx, documentEvents); return documents; } @@ -69,11 +82,13 @@ export async function getOffchainDocument( docId: string, repository: Repository, ): Promise> { + logger.trace({ docId }, "Getting document from offchain storage by id"); const offchainDocuments = await getAllDocumentsFromOffchainStorage(ctx, repository); if (Result.isErr(offchainDocuments)) { return new VError(offchainDocuments, "get all offchain documents failed"); } + const isErr = offchainDocuments.find((d) => Result.isErr(d)); if (isErr) return new VError("get all offchain documents failed"); diff --git a/api/src/service/domain/document/document_share.spec.ts b/api/src/service/domain/document/document_share.spec.ts index 04a7a2939..7bf12aed6 100644 --- a/api/src/service/domain/document/document_share.spec.ts +++ b/api/src/service/domain/document/document_share.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { UserRecord } from "../organization/user_record"; diff --git a/api/src/service/domain/document/document_share.ts b/api/src/service/domain/document/document_share.ts index 75096db0f..3655be88b 100644 --- a/api/src/service/domain/document/document_share.ts +++ b/api/src/service/domain/document/document_share.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; import { config } from "../../../config"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; @@ -8,6 +8,7 @@ import * as DocumentShared from "./document_shared"; import * as Workflowitem from "../workflow/workflowitem"; import { NotAuthorized } from "../errors/not_authorized"; import { PreconditionError } from "../errors/precondition_error"; +import logger from "lib/logger"; type Base64String = string; @@ -38,48 +39,56 @@ export async function shareDocument( issuer: ServiceUser, requestData: RequestData, repository: Repository, -): Promise> { +): Promise> { + logger.trace({ req: requestData }, "Sharing document"); const { organization, docId, projectId, subprojectId, workflowitemId } = requestData; const publisherOrganization = config.organization; - // if secret is already published for this document and organization no event is created + logger.trace("Checking if secret for this document and organization is already published"); const alreadyPublished = await repository.secretAlreadyExists(docId, organization); if (alreadyPublished) { - return undefined; + return new VError("Secret already published for this document"); } - // get the secret for this docId and the organization of the publisher + logger.trace( + { docId, publisherOrganization }, + "Getting the secret for document and the organization of the publisher", + ); const secret = await repository.getSecret(docId, publisherOrganization); if (Result.isErr(secret)) { return new VError(secret, "cannot get secret for this document and organization"); } + const privateKeyBase64Result = await repository.getPrivateKey(config.organization); if (Result.isErr(privateKeyBase64Result)) { return new VError(privateKeyBase64Result, "cannot get private key"); } + const privateBuff = Buffer.from(privateKeyBase64Result, "base64"); const privateKey = privateBuff.toString("utf8"); - // decrypt secret with own private key + + logger.trace("decrypt secret with private key"); const decryptedSecret = await repository.decryptWithKey(secret.encryptedSecret, privateKey); if (Result.isErr(decryptedSecret)) { return new VError(decryptedSecret, "failed to decrypt secret"); } - // get public key of the organization the document is shared with + logger.trace({ organization }, "Getting public key of organization"); const publicKeyBase64 = await repository.getPublicKey(organization); if (Result.isErr(publicKeyBase64)) { return new VError(publicKeyBase64, "failed to get public key"); } + const publicBuff = Buffer.from(publicKeyBase64, "base64"); const publicKey = publicBuff.toString("utf8"); - // encrypt secret with public key of the organization the document is shared with + logger.trace("Encrypting secret with organization key"); const encryptedSecret = await repository.encryptWithKey(decryptedSecret, publicKey); if (Result.isErr(encryptedSecret)) { return new VError(encryptedSecret, "failed to encrypt secret"); } - // create secret event + logger.trace("Creating new secret publishded event"); const newSecretPublishedEvent = DocumentShared.createEvent( ctx.source, issuer.id, @@ -87,26 +96,12 @@ export async function shareDocument( organization, encryptedSecret, ); + if (Result.isErr(newSecretPublishedEvent)) { return new VError(newSecretPublishedEvent, "cannot publish document secret"); } - if (issuer.id !== "root") { - // check if issuer has the workflowitem.intent.grantPermission permission to be allowed to share documents - const workflowitemResult = await repository.getWorkflowitem( - projectId, - subprojectId, - workflowitemId, - ); - if (Result.isErr(workflowitemResult)) { - return new VError(" Error while fetching workflowitem!"); - } - const workflowitem = workflowitemResult; - const intent = "workflowitem.intent.grantPermission"; - if (!Workflowitem.permits(workflowitem, issuer, [intent])) { - return new NotAuthorized({ ctx, userId: issuer.id, intent, target: workflowitem }); - } - } else { + if (issuer.id === "root") { return new PreconditionError( ctx, newSecretPublishedEvent, @@ -114,5 +109,21 @@ export async function shareDocument( ); } + const workflowitem = await repository.getWorkflowitem(projectId, subprojectId, workflowitemId); + + if (Result.isErr(workflowitem)) { + return new VError(" Error while fetching workflowitem!"); + } + + const intent = "workflowitem.intent.grantPermission"; + + logger.trace( + { issuer, intent, workflowitem }, + "Checking if issuer has permission for intent on workflowite", + ); + if (!Workflowitem.permits(workflowitem, issuer, [intent])) { + return new NotAuthorized({ ctx, userId: issuer.id, intent, target: workflowitem }); + } + return newSecretPublishedEvent; } diff --git a/api/src/service/domain/document/document_shared.ts b/api/src/service/domain/document/document_shared.ts index e8d73f3e8..008bd9235 100644 --- a/api/src/service/domain/document/document_shared.ts +++ b/api/src/service/domain/document/document_shared.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -55,6 +56,7 @@ export function createEvent( encryptedSecret, }; + logger.trace({ event }, "Creating and validating secret_published event"); const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${secretPublishedEventType} event`); diff --git a/api/src/service/domain/document/document_upload.spec.ts b/api/src/service/domain/document/document_upload.spec.ts index acbbf37c0..fdbdd37dc 100644 --- a/api/src/service/domain/document/document_upload.spec.ts +++ b/api/src/service/domain/document/document_upload.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { uploadDocument } from "./document_upload"; diff --git a/api/src/service/domain/document/document_upload.ts b/api/src/service/domain/document/document_upload.ts index 32e5c3eb0..f13ac6fe5 100644 --- a/api/src/service/domain/document/document_upload.ts +++ b/api/src/service/domain/document/document_upload.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; @@ -8,6 +8,7 @@ import * as DocumentShared from "./document_shared"; import * as DocumentUploaded from "./document_uploaded"; import * as UserRecord from "../organization/user_record"; import { PreconditionError } from "../errors/precondition_error"; +import logger from "lib/logger"; export interface RequestData { id: string; @@ -43,53 +44,59 @@ export async function uploadDocument( ): Promise> { const { id, documentBase64, fileName } = requestData; + logger.trace("Getting all documents from repository"); const existingDocuments = await repository.getAllDocuments(); if (Result.isErr(existingDocuments)) { return new VError(existingDocuments, "cannot get documents"); } + logger.trace({ docId: id }, "Checking if document with id already exists"); if (docIdAlreadyExists(existingDocuments, id)) { return new VError(id, "failed to upload document, a document with this id already exists"); } - // check if document is empty string if (documentBase64 === "") { return new VError(documentBase64, "an emtpy document is not allowed"); } - // store base64 of document in external storage + logger.trace("Storing document hash in storage"); const documentStorageServiceResponseResult = await repository.storeDocument( id, fileName, documentBase64, ); + if (Result.isErr(documentStorageServiceResponseResult)) { return new VError(documentStorageServiceResponseResult, "failed to store document"); } + const { secret } = documentStorageServiceResponseResult; if (!secret || Result.isErr(secret)) { return new VError("Failed to get the secret for this document"); } - // fetch issuer organization + logger.trace({ issuer }, "Getting organization of issuer"); const userResult = await repository.getUser(issuer.id); if (Result.isErr(userResult)) { return new VError(issuer.id, "Error getting user"); } + const user = userResult; const organization = user.organization; const publicKeyBase64 = await repository.getPublicKey(organization); + if (Result.isErr(publicKeyBase64)) { return new VError(publicKeyBase64, "failed to get public key"); } + const publicBuff = Buffer.from(publicKeyBase64, "base64"); const publicKey = publicBuff.toString("utf8"); const encryptedSecret = await repository.encryptWithKey(secret, publicKey); + if (Result.isErr(encryptedSecret)) { return new VError(encryptedSecret, "failed to encrypt secret"); } - // create doc Event: "offchain_documents" stream - create document_upload event (docId, filename, orga) const newDocumentUploadedEvent = DocumentUploaded.createEvent( ctx.source, issuer.id, @@ -100,8 +107,8 @@ export async function uploadDocument( if (Result.isErr(newDocumentUploadedEvent)) { return new VError(newDocumentUploadedEvent, "cannot update workflowitem"); } - // create secrets events: Check orga access -> check orga public keys (own public key included) -> encrypt secrets with key and generate event per orga on document_secrets stream (docId, orga, encrypted secret) - // create secret event + + logger.trace("Creating document_shared event with secret"); const newSecretPublishedEvent = DocumentShared.createEvent( ctx.source, issuer.id, diff --git a/api/src/service/domain/document/document_uploaded.ts b/api/src/service/domain/document/document_uploaded.ts index a2f5dfa45..c2370fd14 100644 --- a/api/src/service/domain/document/document_uploaded.ts +++ b/api/src/service/domain/document/document_uploaded.ts @@ -2,10 +2,11 @@ import Joi = require("joi"); import { VError } from "verror"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { EventSourcingError } from "../errors/event_sourcing_error"; import { GenericDocument } from "./document"; import { config } from "../../../config"; +import logger from "lib/logger"; type DocumentEventTypeType = "document_uploaded"; const documentEventType: DocumentEventTypeType = "document_uploaded"; @@ -59,6 +60,8 @@ export function createEvent( organization, }; + logger.trace({ event }, "Created document_uploaded event"); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${documentEventType} event`); diff --git a/api/src/service/domain/document/document_validate.spec.ts b/api/src/service/domain/document/document_validate.spec.ts index 6255c0a6c..d58e782c1 100644 --- a/api/src/service/domain/document/document_validate.spec.ts +++ b/api/src/service/domain/document/document_validate.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/document/document_validate.ts b/api/src/service/domain/document/document_validate.ts index 4b49df86b..b0117ded2 100644 --- a/api/src/service/domain/document/document_validate.ts +++ b/api/src/service/domain/document/document_validate.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,8 +15,8 @@ import * as Subproject from "../workflow/subproject"; import * as DocumentValidated from "./document_validated"; import * as Workflowitem from "../workflow/workflowitem"; import * as WorkflowitemEventSourcing from "../workflow/workflowitem_eventsourcing"; -import { GenericDocument } from "./document"; import { getAllDocuments } from "./document_get"; +import logger from "lib/logger"; interface Repository { getWorkflowitem(workflowitemId: Workflowitem.Id): Promise>; @@ -35,22 +35,23 @@ export async function documentValidate( workflowitemId: Workflowitem.Id, repository: Repository, ): Promise> { + logger.trace({ documentId }, "Validating document with id"); const workflowitem = await repository.getWorkflowitem(workflowitemId); if (Result.isErr(workflowitem)) { return new NotFound(ctx, "workflowitem", workflowitemId); } - // Check if document exists + logger.trace("Checking if document exists"); const allDocumentIds = await getAllDocuments(ctx, repository); if (Result.isErr(allDocumentIds)) { return new VError(allDocumentIds, "failed to fetch all documents"); } - const hasDocument = allDocumentIds.find(doc => doc.id === documentId); + + const hasDocument = allDocumentIds.find((doc) => doc.id === documentId); if (!hasDocument) { return new NotFound(ctx, "document", documentId); } - // Create the new event: const documentValidatedEvent = DocumentValidated.createEvent( isDocumentValid, documentId, @@ -64,12 +65,15 @@ export async function documentValidate( return new VError(documentValidatedEvent, "failed to create event in domain"); } - // Root user cannot validate a document if (issuer.id === "root") { - return new PreconditionError(ctx, documentValidatedEvent, "'root' user cannot validate a document"); + return new PreconditionError( + ctx, + documentValidatedEvent, + "'root' user cannot validate a document", + ); } - // Check that the new event is indeed valid: + logger.trace("Checking if document is valid"); const result = WorkflowitemEventSourcing.newWorkflowitemFromEvent( ctx, workflowitem, @@ -79,33 +83,38 @@ export async function documentValidate( return new InvalidCommand(ctx, documentValidatedEvent, [result]); } - // Create notification events: let notifications: Result.Type = []; - if (workflowitem.assignee !== undefined) { - const recipientsResult = await repository.getUsersForIdentity(workflowitem.assignee); - if (Result.isErr(recipientsResult)) { - return new VError(recipientsResult, `fetch users for ${workflowitem.assignee} failed`); - } - notifications = recipientsResult.reduce((notifications, recipient) => { - // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { - const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - documentValidatedEvent, - projectId, - ); - if (Result.isErr(notification)) { - return new VError(notification, "failed to create notification event"); - } - notifications.push(notification); - } - return notifications; - }, [] as NotificationCreated.Event[]); + if (!workflowitem.assignee) { + return { newEvents: [documentValidatedEvent], workflowitem }; + } + + logger.trace({ assignee: workflowitem.assignee }, "Creating notification events for assignee"); + const recipientsResult = await repository.getUsersForIdentity(workflowitem.assignee); + if (Result.isErr(recipientsResult)) { + return new VError(recipientsResult, `fetch users for ${workflowitem.assignee} failed`); } + + notifications = recipientsResult.reduce((notifications, recipient) => { + // The issuer doesn't receive a notification: + if (recipient !== issuer.id) { + const notification = NotificationCreated.createEvent( + ctx.source, + issuer.id, + recipient, + documentValidatedEvent, + projectId, + ); + if (Result.isErr(notification)) { + return new VError(notification, "failed to create notification event"); + } + notifications.push(notification); + } + return notifications; + }, [] as NotificationCreated.Event[]); + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification events"); } + return { newEvents: [documentValidatedEvent, ...notifications], workflowitem }; } diff --git a/api/src/service/domain/document/secret.spec.ts b/api/src/service/domain/document/secret.spec.ts index 48e25bec9..86c77f4b0 100644 --- a/api/src/service/domain/document/secret.spec.ts +++ b/api/src/service/domain/document/secret.spec.ts @@ -1,74 +1,81 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { getAllSecrets, getSecret, secretAlreadyExists } from "./secret_get"; const ctx: Ctx = { - requestId: "test", - source: "test", + requestId: "test", + source: "test", }; const existingOrganization = "organization"; const existingDocument = "1001"; const encryptedSecret = "encryptedSecret"; const secretPublishedEvent: BusinessEvent = { - type: "secret_published", - source: "", - time: "", - publisher: "", - docId: existingDocument, - organization: existingOrganization, - encryptedSecret: encryptedSecret, + type: "secret_published", + source: "", + time: "", + publisher: "", + docId: existingDocument, + organization: existingOrganization, + encryptedSecret: encryptedSecret, }; - const repository = { - getSecretPublishedEvents: () => Promise.resolve([secretPublishedEvent]), + getSecretPublishedEvents: () => Promise.resolve([secretPublishedEvent]), }; describe("Get secrets for storage-service", async () => { - it("Get all secrets works", async () => { - const result = await getAllSecrets(ctx, repository); - - assert.isTrue(Result.isOk(result)); - expect(result[0]).to.not.equal(undefined); - expect(result[0]).to.include({ "docId": existingDocument }); - expect(result[0]).to.include({ "organization": existingOrganization }); - expect(result[0]).to.include({ "encryptedSecret": encryptedSecret }); - }); - - it("Get an existing secret works", async () => { - const result = await getSecret(ctx, existingDocument, existingOrganization, repository); - - assert.isTrue(Result.isOk(result)); - expect(result).to.not.equal(undefined); - expect(result).to.include({ "docId": existingDocument }); - expect(result).to.include({ "organization": existingOrganization }); - expect(result).to.include({ "encryptedSecret": encryptedSecret }); - }); - - it("Get a non-existing secret fails", async () => { - const nonExtistingDocument = "non-existing"; - const result = await getSecret(ctx, nonExtistingDocument, existingOrganization, repository); - - assert.isTrue(Result.isErr(result)); - }); - - it("Check if an existing documents exists", async () => { - const result = await secretAlreadyExists(ctx, existingDocument, existingOrganization, repository); - - assert.isTrue(Result.isOk(result)); - expect(result).to.eql(true); - }); - - it("Check if a non-existing documents exists", async () => { - const nonExtistingDocument = "non-existing"; - const result = await secretAlreadyExists(ctx, nonExtistingDocument, existingOrganization, repository); - - assert.isTrue(Result.isOk(result)); - expect(result).to.eql(false); - }); - - + it("Get all secrets works", async () => { + const result = await getAllSecrets(ctx, repository); + + assert.isTrue(Result.isOk(result)); + expect(result[0]).to.not.equal(undefined); + expect(result[0]).to.include({ docId: existingDocument }); + expect(result[0]).to.include({ organization: existingOrganization }); + expect(result[0]).to.include({ encryptedSecret: encryptedSecret }); + }); + + it("Get an existing secret works", async () => { + const result = await getSecret(ctx, existingDocument, existingOrganization, repository); + + assert.isTrue(Result.isOk(result)); + expect(result).to.not.equal(undefined); + expect(result).to.include({ docId: existingDocument }); + expect(result).to.include({ organization: existingOrganization }); + expect(result).to.include({ encryptedSecret: encryptedSecret }); + }); + + it("Get a non-existing secret fails", async () => { + const nonExtistingDocument = "non-existing"; + const result = await getSecret(ctx, nonExtistingDocument, existingOrganization, repository); + + assert.isTrue(Result.isErr(result)); + }); + + it("Check if an existing documents exists", async () => { + const result = await secretAlreadyExists( + ctx, + existingDocument, + existingOrganization, + repository, + ); + + assert.isTrue(Result.isOk(result)); + expect(result).to.eql(true); + }); + + it("Check if a non-existing documents exists", async () => { + const nonExtistingDocument = "non-existing"; + const result = await secretAlreadyExists( + ctx, + nonExtistingDocument, + existingOrganization, + repository, + ); + + assert.isTrue(Result.isOk(result)); + expect(result).to.eql(false); + }); }); diff --git a/api/src/service/domain/document/secret_get.ts b/api/src/service/domain/document/secret_get.ts index 66b260b64..7e85fb004 100644 --- a/api/src/service/domain/document/secret_get.ts +++ b/api/src/service/domain/document/secret_get.ts @@ -1,10 +1,11 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { sourceSecrets } from "./document_eventsourcing"; import * as DocumentShared from "./document_shared"; import { NotFound } from "../errors/not_found"; +import logger from "lib/logger"; interface Repository { getSecretPublishedEvents(): Promise>; @@ -14,6 +15,7 @@ export async function getAllSecrets( ctx: Ctx, repository: Repository, ): Promise> { + logger.trace("Getting all secrets"); const secretEvents = await repository.getSecretPublishedEvents(); if (Result.isErr(secretEvents)) { return new VError(secretEvents, "fetch secrets_published events failed"); diff --git a/api/src/service/domain/document/storage_service_url_eventsourcing.ts b/api/src/service/domain/document/storage_service_url_eventsourcing.ts index 97d8f184d..742c8d37c 100644 --- a/api/src/service/domain/document/storage_service_url_eventsourcing.ts +++ b/api/src/service/domain/document/storage_service_url_eventsourcing.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { BusinessEvent } from "../business_event"; export function sourceStorageServiceUrls(events: BusinessEvent[]): Map { @@ -10,6 +11,7 @@ export function sourceStorageServiceUrls(events: BusinessEvent[]): Map, event: BusinessEvent) { + logger.trace("Applying storage service url"); if (event.type === "storage_service_url_published") { const { organization, organizationUrl } = event; urls.set(organization, organizationUrl); diff --git a/api/src/service/domain/document/storage_service_url_get.spec.ts b/api/src/service/domain/document/storage_service_url_get.spec.ts index ec620b196..ad53f76eb 100644 --- a/api/src/service/domain/document/storage_service_url_get.spec.ts +++ b/api/src/service/domain/document/storage_service_url_get.spec.ts @@ -1,45 +1,43 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { getAllStorageServiceUrls, getStorageServiceUrl } from "./storage_service_url_get"; const ctx: Ctx = { - requestId: "test", - source: "test", + requestId: "test", + source: "test", }; const existingOrganization = "organization"; const existingOrganizationUrl = "organizationUrl"; const storageServiceUrlEvent: BusinessEvent = { - type: "storage_service_url_published", - source: "string", - time: "string", - publisher: "alice", - organization: existingOrganization, - organizationUrl: existingOrganizationUrl, + type: "storage_service_url_published", + source: "string", + time: "string", + publisher: "alice", + organization: existingOrganization, + organizationUrl: existingOrganizationUrl, }; const repository = { - getStorageServiceUrlPublishedEvents: () => Promise.resolve([storageServiceUrlEvent]), + getStorageServiceUrlPublishedEvents: () => Promise.resolve([storageServiceUrlEvent]), }; - describe("Storage service URL", () => { - it("Get all storage service URLs", async () => { - const result = await getAllStorageServiceUrls(repository); - assert.isTrue(Result.isOk(result)); - }); - - it("Get storage service URL from existing organization works", async () => { - const result = await getStorageServiceUrl(existingOrganization, repository); - assert.isTrue(Result.isOk(result)); - }); - - it("Get storage service URL from non-existing organization returns undefined", async () => { - const result = await getStorageServiceUrl("non-existing-organization", repository); - assert.isTrue(Result.isOk(result)); - expect(result).to.eql(undefined); - }); - + it("Get all storage service URLs", async () => { + const result = await getAllStorageServiceUrls(repository); + assert.isTrue(Result.isOk(result)); + }); + + it("Get storage service URL from existing organization works", async () => { + const result = await getStorageServiceUrl(existingOrganization, repository); + assert.isTrue(Result.isOk(result)); + }); + + it("Get storage service URL from non-existing organization returns undefined", async () => { + const result = await getStorageServiceUrl("non-existing-organization", repository); + assert.isTrue(Result.isOk(result)); + expect(result).to.eql(undefined); + }); }); diff --git a/api/src/service/domain/document/storage_service_url_get.ts b/api/src/service/domain/document/storage_service_url_get.ts index fe9656b6e..e9108435d 100644 --- a/api/src/service/domain/document/storage_service_url_get.ts +++ b/api/src/service/domain/document/storage_service_url_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; @@ -10,10 +11,14 @@ interface Repository { export async function getAllStorageServiceUrls( repository: Repository, ): Promise>> { + logger.trace("Fetching all storage service urls ..."); + const organizationUrlEvents = await repository.getStorageServiceUrlPublishedEvents(); if (Result.isErr(organizationUrlEvents)) { return new VError(organizationUrlEvents, "cannot get events"); } + logger.trace("Sourcing storage service urls ..."); + const urls = sourceStorageServiceUrls(organizationUrlEvents); return urls; } @@ -22,6 +27,8 @@ export async function getStorageServiceUrl( organization, repository, ): Promise> { + logger.trace("Fetching storage service urls ..."); + const urls = await getAllStorageServiceUrls(repository); if (Result.isErr(urls)) { return new VError(urls, "cannot source urls"); diff --git a/api/src/service/domain/document/storage_service_url_update.spec.ts b/api/src/service/domain/document/storage_service_url_update.spec.ts index 4a62979ec..6827e897f 100644 --- a/api/src/service/domain/document/storage_service_url_update.spec.ts +++ b/api/src/service/domain/document/storage_service_url_update.spec.ts @@ -1,24 +1,23 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { storageServiceUrlPublish } from "./storage_service_url_update"; const ctx: Ctx = { - requestId: "test", - source: "test", + requestId: "test", + source: "test", }; const alice: ServiceUser = { id: "alice", groups: ["alice"], address: "address" }; - describe("Storage service URL update/publish", () => { - it("Publish a new storage-service", async () => { - const requestData = { - organization: "string", - organizationUrl: "string", - }; - const result = await storageServiceUrlPublish(ctx, alice, requestData); - assert.isTrue(Result.isOk(result)); - }); + it("Publish a new storage-service", async () => { + const requestData = { + organization: "string", + organizationUrl: "string", + }; + const result = await storageServiceUrlPublish(ctx, alice, requestData); + assert.isTrue(Result.isOk(result)); + }); }); diff --git a/api/src/service/domain/document/storage_service_url_update.ts b/api/src/service/domain/document/storage_service_url_update.ts index 75296f480..aadb62ea5 100644 --- a/api/src/service/domain/document/storage_service_url_update.ts +++ b/api/src/service/domain/document/storage_service_url_update.ts @@ -1,7 +1,8 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; @@ -27,6 +28,7 @@ export async function storageServiceUrlPublish( requestData: RequestData, ): Promise> { const { organization, organizationUrl } = requestData; + logger.trace("Creating event: offchain_documents"); // create Event: "offchain_documents" stream - create storage_service_url_published event (organization, url) const newUrlUpdatedEvent = StorageServiceUrlUpdated.createEvent( diff --git a/api/src/service/domain/document/workflowitem_document_download.ts b/api/src/service/domain/document/workflowitem_document_download.ts index 4fe23321c..ab256adb9 100644 --- a/api/src/service/domain/document/workflowitem_document_download.ts +++ b/api/src/service/domain/document/workflowitem_document_download.ts @@ -1,15 +1,16 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; +import { config } from "../../../config"; import * as Result from "../../../result"; +import * as DocumentUploaded from "../document/document_uploaded"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; import { ServiceUser } from "../organization/service_user"; import * as Workflowitem from "../workflow/workflowitem"; +import { UploadedDocument } from "./document"; import * as DocumentShared from "./document_shared"; -import { config } from "../../../config"; -import * as DocumentUploaded from "../document/document_uploaded"; import VError = require("verror"); -import { UploadedDocument } from "./document"; type Base64String = string; interface DocumentStorageServiceResponse { @@ -40,6 +41,8 @@ async function getDocumentFromInternalOrExternalStorage( workflowitem, ): Promise> { // Get all events from one document + logger.trace("Fetching document: ", documentId, " from internal *or* external storage ..."); + const documentInfo = await repository.getDocumentInfo(documentId); if (!documentInfo || Result.isErr(documentInfo)) { @@ -50,6 +53,7 @@ async function getDocumentFromInternalOrExternalStorage( } //get secret from stream + logger.trace("Fetching secret from stream for document: ", documentId); const encryptedSecret = await repository.getSecret(documentId, config.organization); if (!encryptedSecret || Result.isErr(encryptedSecret)) { return new VError( @@ -59,6 +63,8 @@ async function getDocumentFromInternalOrExternalStorage( } // decrypt secret with own private key + logger.trace("Decrypting document: ", documentId); + const privateKeyBase64Result = await repository.getPrivateKey(config.organization); if (Result.isErr(privateKeyBase64Result)) { return new VError(privateKeyBase64Result, "cannot get private key"); @@ -103,6 +109,8 @@ export async function getDocument( documentId: string, repository: Repository, ): Promise> { + logger.trace("Fetching document: ", documentId, "..."); + // check for permissions etc const workflowitem = await repository.getWorkflowitem(workflowitemId); if (Result.isErr(workflowitem)) { @@ -121,6 +129,8 @@ export async function getDocument( `workfowitem ${workflowitem.displayName} has no link to document`, ); } + logger.trace("Trying to find document: ", documentId, "offchain ..."); + // Try to get event from offchain storage const offchainDocument = await repository.getOffchainDocument(documentId); if (Result.isErr(offchainDocument)) { @@ -131,6 +141,8 @@ export async function getDocument( } if (!offchainDocument) { + logger.trace("Trying to find document: ", documentId, "via storage service ..."); + // Try to get document from storage service const documentFromStorage = await getDocumentFromInternalOrExternalStorage( ctx, diff --git a/api/src/service/domain/document/workflowitem_document_uploaded.ts b/api/src/service/domain/document/workflowitem_document_uploaded.ts index 699083609..5f12c1e17 100644 --- a/api/src/service/domain/document/workflowitem_document_uploaded.ts +++ b/api/src/service/domain/document/workflowitem_document_uploaded.ts @@ -6,7 +6,7 @@ import * as WorkflowitemDocument from "./document"; import * as Project from "../workflow/project"; import * as Subproject from "../workflow/subproject"; import * as Workflowitem from "../workflow/workflowitem"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { EventSourcingError } from "../errors/event_sourcing_error"; type EventTypeType = "workflowitem_document_uploaded"; diff --git a/api/src/service/domain/errors/already_exists.ts b/api/src/service/domain/errors/already_exists.ts index 8b0c50cfc..a5dcb4ca9 100644 --- a/api/src/service/domain/errors/already_exists.ts +++ b/api/src/service/domain/errors/already_exists.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { BusinessEvent } from "../business_event"; /** diff --git a/api/src/service/domain/errors/event_sourcing_error.ts b/api/src/service/domain/errors/event_sourcing_error.ts index ae73048b2..3a4c6b78a 100644 --- a/api/src/service/domain/errors/event_sourcing_error.ts +++ b/api/src/service/domain/errors/event_sourcing_error.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { BusinessEvent } from "../business_event"; interface Info { diff --git a/api/src/service/domain/errors/invalid_command.ts b/api/src/service/domain/errors/invalid_command.ts index 8796f809b..a80231578 100644 --- a/api/src/service/domain/errors/invalid_command.ts +++ b/api/src/service/domain/errors/invalid_command.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { BusinessEvent } from "../business_event"; export class InvalidCommand extends Error { @@ -9,7 +9,9 @@ export class InvalidCommand extends Error { ) { // TODO this shouldn't be failed to apply event but failed to execute intent super( - `Failed to apply ${businessEvent.type}: ${validationErrors.map(e => e.message).join("; ")}.`, + `Failed to apply ${businessEvent.type}: ${validationErrors + .map((e) => e.message) + .join("; ")}.`, ); // Maintains proper stack trace for where our error was thrown (only available on V8): diff --git a/api/src/service/domain/errors/invalid_event.ts b/api/src/service/domain/errors/invalid_event.ts index b89251776..0499bf274 100644 --- a/api/src/service/domain/errors/invalid_event.ts +++ b/api/src/service/domain/errors/invalid_event.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { BusinessEvent } from "../business_event"; export class InvalidEvent extends Error { @@ -8,7 +8,9 @@ export class InvalidEvent extends Error { private readonly validationErrors: Error[], ) { super( - `Failed to apply ${businessEvent.type}: ${validationErrors.map(e => e.message).join("; ")}.`, + `Failed to apply ${businessEvent.type}: ${validationErrors + .map((e) => e.message) + .join("; ")}.`, ); // Maintains proper stack trace for where our error was thrown (only available on V8): diff --git a/api/src/service/domain/errors/not_authorized.ts b/api/src/service/domain/errors/not_authorized.ts index e4adf6231..08e43f2b5 100644 --- a/api/src/service/domain/errors/not_authorized.ts +++ b/api/src/service/domain/errors/not_authorized.ts @@ -2,7 +2,7 @@ import { isArray } from "util"; import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; interface Info { ctx: Ctx; diff --git a/api/src/service/domain/errors/not_found.ts b/api/src/service/domain/errors/not_found.ts index b42bda8e9..d8b63f741 100644 --- a/api/src/service/domain/errors/not_found.ts +++ b/api/src/service/domain/errors/not_found.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; export class NotFound extends Error { constructor( diff --git a/api/src/service/domain/errors/precondition_error.ts b/api/src/service/domain/errors/precondition_error.ts index ec67a268b..d48f23ef2 100644 --- a/api/src/service/domain/errors/precondition_error.ts +++ b/api/src/service/domain/errors/precondition_error.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import { BusinessEvent } from "../business_event"; export class PreconditionError extends Error { diff --git a/api/src/service/domain/network/node_declined.ts b/api/src/service/domain/network/node_declined.ts index 012f77ea2..1cd540b00 100644 --- a/api/src/service/domain/network/node_declined.ts +++ b/api/src/service/domain/network/node_declined.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -48,6 +48,8 @@ export function createEvent( declinerOrganization, time, }; + logger.trace("Creating node declinded event"); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/network/node_registered.ts b/api/src/service/domain/network/node_registered.ts index c74064718..7129b2b06 100644 --- a/api/src/service/domain/network/node_registered.ts +++ b/api/src/service/domain/network/node_registered.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -18,12 +18,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), address: Joi.string().required(), organization: Joi.string().required(), @@ -35,7 +31,7 @@ export function createEvent( address: string, organization: string, time: string = new Date().toISOString(), -): Result.Type { +): Result.Type { const event = { type: eventType, source, @@ -44,6 +40,8 @@ export function createEvent( organization, time, }; + logger.trace("Creating node registered event"); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/network/nodes_logged.ts b/api/src/service/domain/network/nodes_logged.ts index 325ba40c9..40daebe2e 100644 --- a/api/src/service/domain/network/nodes_logged.ts +++ b/api/src/service/domain/network/nodes_logged.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import * as Result from "../../../result"; @@ -20,11 +21,13 @@ export const schema = Joi.object({ }).options({ stripUnknown: true }); export function createEvent(type: EventTypeType, date: string, peers: any[]): Result.Type { + logger.trace("Creating node logged event"); const event = { type: eventType, date, peers, }; + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/organization/auth_token.ts b/api/src/service/domain/organization/auth_token.ts index e4db56cc6..df1474389 100644 --- a/api/src/service/domain/organization/auth_token.ts +++ b/api/src/service/domain/organization/auth_token.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { globalIntents } from "../../../authz/intents"; import * as Result from "../../../result"; @@ -35,21 +36,27 @@ export async function fromUserRecord( user: UserRecord.UserRecord, repository: Repository, ): Promise> { + logger.trace({ user }, "Getting groups of user by userrecord"); const groupsResult = await repository.getGroupsForUser(user.id); if (Result.isErr(groupsResult)) { return new VError(groupsResult, `fetch groups for user ${user.id} failed`); } - const groups = groupsResult; + logger.trace({ organization: user.organization }, "Getting organization address"); + const groups = groupsResult; const organizationAddressResult = await repository.getOrganizationAddress(user.organization); if (Result.isErr(organizationAddressResult)) { return new VError(organizationAddressResult, "get organization address failed"); } + + logger.trace("Getting global permissions"); const organizationAddress = organizationAddressResult; const globalPermissionsResult = await repository.getGlobalPermissions(); if (Result.isErr(globalPermissionsResult)) { return new VError(globalPermissionsResult, "get global permissions failed"); } + + logger.trace("Getting allowed Intents"); const globalPermissions = globalPermissionsResult; const allowedIntents = globalIntents.filter((intent) => { const eligibleIdentities = identitiesAuthorizedFor(globalPermissions, intent); @@ -57,6 +64,7 @@ export async function fromUserRecord( canAssumeIdentity({ id: user.id, groups }, identity), ); }); + return { userId: user.id, displayName: user.displayName, diff --git a/api/src/service/domain/organization/group_create.spec.ts b/api/src/service/domain/organization/group_create.spec.ts index 8c6721735..f901bb4f5 100644 --- a/api/src/service/domain/organization/group_create.spec.ts +++ b/api/src/service/domain/organization/group_create.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { AlreadyExists } from "../errors/already_exists"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/organization/group_create.ts b/api/src/service/domain/organization/group_create.ts index 0420e8735..00272c29d 100644 --- a/api/src/service/domain/organization/group_create.ts +++ b/api/src/service/domain/organization/group_create.ts @@ -1,8 +1,9 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { groupIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { BusinessEvent } from "../business_event"; @@ -53,6 +54,8 @@ export async function createGroup( ): Promise> { const source = ctx.source; const publisher = creatingUser.id; + + logger.trace({ req: data }, "Trying to create 'GroupCreated' Event from request data"); const createEvent = GroupCreated.createEvent(source, publisher, { id: data.id, displayName: data.displayName, @@ -61,17 +64,19 @@ export async function createGroup( permissions: newDefaultPermissionsFor(creatingUser), additionalData: data.additionalData || {}, }); + if (Result.isErr(createEvent)) { return new VError(createEvent, "failed to create group created event"); } - // Check group already exists: + logger.trace({ event: createEvent }, "Checking if group alredy exists"); const groupExistsResult = await repository.groupExists(createEvent.group.id); if (Result.isErr(groupExistsResult)) { return new VError(groupExistsResult, "groupExists check failed"); } + const groupExists = groupExistsResult; - // Check user already exists: + logger.trace({ event: createEvent }, "Checking if user with name of the group alredy exists"); const userExistsResult = await repository.userExists(createEvent.group.id); if (Result.isErr(userExistsResult)) { return new VError(userExistsResult, "user exists check failed"); @@ -82,12 +87,15 @@ export async function createGroup( } // Check authorization (if not root): + logger.trace({ user: creatingUser }, "Checking if user is root-user"); if (creatingUser.id !== "root") { const intent = "global.createGroup"; const globalPermissionsResult = await repository.getGlobalPermissions(); + if (Result.isErr(globalPermissionsResult)) { return new VError(globalPermissionsResult, "get global permissions failed"); } + const globalPermissions = globalPermissionsResult; const isAuthorized = identitiesAuthorizedFor(globalPermissions, intent).some((identity) => canAssumeIdentity(creatingUser, identity), diff --git a/api/src/service/domain/organization/group_created.ts b/api/src/service/domain/organization/group_created.ts index 367128aea..1d5247269 100644 --- a/api/src/service/domain/organization/group_created.ts +++ b/api/src/service/domain/organization/group_created.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { Identity } from "../organization/identity"; @@ -23,9 +23,7 @@ interface InitialData { const initialDataSchema = Joi.object({ id: Group.idSchema.required(), displayName: Joi.string().required(), - description: Joi.string() - .allow("") - .required(), + description: Joi.string().allow("").required(), members: Group.membersSchema.required(), permissions: permissionsSchema.required(), additionalData: AdditionalData.schema.required(), @@ -41,12 +39,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), group: initialDataSchema.required(), }); @@ -57,6 +51,8 @@ export function createEvent( group: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating group created event..."); + const event = { type: eventType, source, diff --git a/api/src/service/domain/organization/group_eventsourcing.ts b/api/src/service/domain/organization/group_eventsourcing.ts index 13bad2173..0551e18c3 100644 --- a/api/src/service/domain/organization/group_eventsourcing.ts +++ b/api/src/service/domain/organization/group_eventsourcing.ts @@ -1,5 +1,6 @@ -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -18,6 +19,7 @@ export function sourceGroups( const groups = new Map(); const errors: EventSourcingError[] = []; for (const event of events) { + logger.trace({ event }, "Validating group Event by applying it"); apply(ctx, groups, event, errors); } return { groups: [...groups.values()], errors }; @@ -89,6 +91,8 @@ function applyMemberAdded( memberAdded: GroupMemberAdded.Event, errors: EventSourcingError[], ) { + logger.trace("Adding member to group..."); + const group = deepcopy(groups.get(memberAdded.groupId)); if (group === undefined) return; @@ -123,12 +127,14 @@ function applyMemberRemoved( memberRemoved: GroupMemberRemoved.Event, errors: EventSourcingError[], ) { + logger.trace("Remove member from group..."); + const group = deepcopy(groups.get(memberRemoved.groupId)); if (group === undefined) return; const memberIdx = group.members.indexOf(memberRemoved.member); if (memberIdx === -1) { - // The "member" already doesn't belong to this group, so there's nothing left to do. + logger.trace("The member to remove does not belong to this group => all good!"); return; } // Remove the user from the array: @@ -139,6 +145,7 @@ function applyMemberRemoved( errors.push(new EventSourcingError({ ctx, event: memberRemoved }, result)); return; } + logger.trace("Publishing member removal ..."); const traceEvent: GroupTraceEvent = { entityId: memberRemoved.groupId, @@ -159,6 +166,8 @@ function applyPermissionGranted( permissionGranted: GroupPermissionGranted.Event, errors: EventSourcingError[], ) { + logger.trace("Applying group permissions ..."); + const group = deepcopy(groups.get(permissionGranted.groupId)); if (group === undefined) return; @@ -173,6 +182,7 @@ function applyPermissionGranted( errors.push(new EventSourcingError({ ctx, event: permissionGranted }, result)); return; } + logger.trace("Publishing group permissions ..."); const traceEvent: GroupTraceEvent = { entityId: permissionGranted.groupId, @@ -193,6 +203,8 @@ function applyPermissionRevoked( permissionRevoked: GroupPermissionRevoked.Event, errors: EventSourcingError[], ) { + logger.trace("Applying group permissions revoke ..."); + const group = deepcopy(groups.get(permissionRevoked.groupId)); if (group === undefined) return; @@ -211,6 +223,7 @@ function applyPermissionRevoked( errors.push(new EventSourcingError({ ctx, event: permissionRevoked }, result)); return; } + logger.trace("Publishing group permissions revoke..."); const traceEvent: GroupTraceEvent = { entityId: permissionRevoked.groupId, diff --git a/api/src/service/domain/organization/group_get.ts b/api/src/service/domain/organization/group_get.ts index 12b87ccf9..aa3716914 100644 --- a/api/src/service/domain/organization/group_get.ts +++ b/api/src/service/domain/organization/group_get.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotFound } from "../errors/not_found"; @@ -16,11 +17,12 @@ export async function getOneGroup( groupId: Group.Id, repository: Repository, ): Promise> { + logger.trace({ groupId }, "Getting group by id"); const allEvents = await repository.getGroupEvents(); // Errors are ignored here: const { groups } = sourceGroups(ctx, allEvents); - const group = groups.find(x => x.id === groupId); + const group = groups.find((x) => x.id === groupId); if (group === undefined) { return Promise.reject(new NotFound(ctx, "group", groupId)); } @@ -36,6 +38,8 @@ export async function getAllGroups( user: ServiceUser, repository: Repository, ): Promise { + logger.trace("Fetching all group events *NOTE* errors are ignored in this procedure!"); + const allEvents = await repository.getGroupEvents(); // Errors are ignored here: const { groups } = sourceGroups(ctx, allEvents); diff --git a/api/src/service/domain/organization/group_member_add.spec.ts b/api/src/service/domain/organization/group_member_add.spec.ts index d1de212f9..0810158bc 100644 --- a/api/src/service/domain/organization/group_member_add.spec.ts +++ b/api/src/service/domain/organization/group_member_add.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/organization/group_member_add.ts b/api/src/service/domain/organization/group_member_add.ts index 1cfc2a7ad..5a278931d 100644 --- a/api/src/service/domain/organization/group_member_add.ts +++ b/api/src/service/domain/organization/group_member_add.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -9,6 +9,8 @@ import { ServiceUser } from "../organization/service_user"; import * as Group from "./group"; import { sourceGroups } from "./group_eventsourcing"; import * as GroupMemberAdded from "./group_member_added"; +import logger from "lib/logger"; +import { trace } from "console"; interface Repository { getGroupEvents(): Promise; @@ -24,18 +26,19 @@ export async function addMember( const groupEvents = await repository.getGroupEvents(); const { groups } = sourceGroups(ctx, groupEvents); - // Check if group exists - const group = groups.find(x => x.id === groupId); + logger.trace({ groupId }, "Checking if group exists"); + const group = groups.find((x) => x.id === groupId); if (group === undefined) { return new NotFound(ctx, "group", groupId); } - // Create the new event: + logger.trace("Creating new GroupMemberAdded event"); const memberAdded = GroupMemberAdded.createEvent(ctx.source, issuer.id, groupId, newMember); if (Result.isErr(memberAdded)) { return new VError(memberAdded, "failed to create group added event"); } - // Check authorization (if not root): + + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const intent = "group.addUser"; if (!Group.permits(group, issuer, [intent])) { @@ -43,7 +46,7 @@ export async function addMember( } } - // Check that the new event is indeed valid: + logger.trace("Checking that the groupEvents are valid"); const { errors } = sourceGroups(ctx, groupEvents.concat([memberAdded])); if (errors.length > 0) { return new InvalidCommand(ctx, memberAdded, errors); diff --git a/api/src/service/domain/organization/group_member_added.ts b/api/src/service/domain/organization/group_member_added.ts index f48a1ce5c..75a2a1937 100644 --- a/api/src/service/domain/organization/group_member_added.ts +++ b/api/src/service/domain/organization/group_member_added.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Group from "./group"; @@ -19,12 +19,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), groupId: Group.idSchema.required(), newMember: Group.memberSchema.required(), @@ -36,7 +32,7 @@ export function createEvent( groupId: Group.Id, newMember: Group.Member, time: string = new Date().toISOString(), -): Result.Type { +): Result.Type { const event = { type: eventType, source, @@ -45,6 +41,8 @@ export function createEvent( newMember, time, }; + logger.trace("Creating group_member_add event..."); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/organization/group_member_remove.spec.ts b/api/src/service/domain/organization/group_member_remove.spec.ts index 70b342028..fd5e3e3b8 100644 --- a/api/src/service/domain/organization/group_member_remove.spec.ts +++ b/api/src/service/domain/organization/group_member_remove.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/organization/group_member_remove.ts b/api/src/service/domain/organization/group_member_remove.ts index ec3a39b3c..e04135199 100644 --- a/api/src/service/domain/organization/group_member_remove.ts +++ b/api/src/service/domain/organization/group_member_remove.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -9,6 +9,7 @@ import { ServiceUser } from "../organization/service_user"; import * as Group from "./group"; import { sourceGroups } from "./group_eventsourcing"; import * as GroupMemberRemoved from "./group_member_removed"; +import logger from "lib/logger"; interface Repository { getGroupEvents(): Promise; @@ -24,17 +25,18 @@ export async function removeMember( const groupEvents = await repository.getGroupEvents(); const { groups } = sourceGroups(ctx, groupEvents); - const group = groups.find(x => x.id === groupId); + const group = groups.find((x) => x.id === groupId); if (group === undefined) { return new NotFound(ctx, "group", groupId); } - // Create the new event: + logger.trace({ groupId, issuer }, "Creating GroupMemberRemoved Event"); const memberRemoved = GroupMemberRemoved.createEvent(ctx.source, issuer.id, groupId, newMember); if (Result.isErr(memberRemoved)) { return new VError(memberRemoved, "failed to create group member removed event"); } - // Check authorization (if not root): + + logger.trace("Check if user is root"); if (issuer.id !== "root") { const intent = "group.removeUser"; if (!Group.permits(group, issuer, [intent])) { @@ -42,7 +44,7 @@ export async function removeMember( } } - // Check that the new event is indeed valid: + logger.trace({ memberRemoved }, "Checking if event is valid"); const { errors } = sourceGroups(ctx, groupEvents.concat([memberRemoved])); if (errors.length > 0) { return new InvalidCommand(ctx, memberRemoved, errors); diff --git a/api/src/service/domain/organization/group_member_removed.ts b/api/src/service/domain/organization/group_member_removed.ts index 3e9e24bf8..c488da23e 100644 --- a/api/src/service/domain/organization/group_member_removed.ts +++ b/api/src/service/domain/organization/group_member_removed.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Group from "./group"; @@ -19,12 +19,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), groupId: Group.idSchema.required(), member: Group.memberSchema.required(), @@ -45,6 +41,8 @@ export function createEvent( member, time, }; + logger.trace("Creating group_member_remove event..."); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/organization/group_permissions_granted.ts b/api/src/service/domain/organization/group_permissions_granted.ts index fb044b1a9..6f36a6be7 100644 --- a/api/src/service/domain/organization/group_permissions_granted.ts +++ b/api/src/service/domain/organization/group_permissions_granted.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { groupIntents } from "../../../authz/intents"; @@ -21,12 +22,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), groupId: Group.idSchema.required(), permission: Joi.valid(groupIntents).required(), @@ -50,10 +47,13 @@ export function createEvent( permission, grantee, }; + + logger.trace({ event }, "Checking validity of event"); const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); } + return event; } diff --git a/api/src/service/domain/organization/group_permissions_revoked.ts b/api/src/service/domain/organization/group_permissions_revoked.ts index 07f50d034..4ba37b8a7 100644 --- a/api/src/service/domain/organization/group_permissions_revoked.ts +++ b/api/src/service/domain/organization/group_permissions_revoked.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { groupIntents } from "../../../authz/intents"; @@ -21,12 +22,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), groupId: Group.idSchema.required(), permission: Joi.valid(groupIntents).required(), @@ -50,10 +47,13 @@ export function createEvent( permission, revokee, }; + + logger.trace({ event }, "Checking validity of event"); const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); } + return event; } diff --git a/api/src/service/domain/organization/public_key_eventsourcing.ts b/api/src/service/domain/organization/public_key_eventsourcing.ts index 579db9205..ced4b09e9 100644 --- a/api/src/service/domain/organization/public_key_eventsourcing.ts +++ b/api/src/service/domain/organization/public_key_eventsourcing.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -39,6 +40,7 @@ function handleCreate( publicKeyPublished: PublicKeyCreated.Event, errors: EventSourcingError[], ) { + logger.trace({ event: publicKeyPublished }, "Handling public_key_published event"); let publicKeyBase64 = keyByOrganization.get(publicKeyPublished.organization); if (publicKeyBase64 !== undefined) { errors.push( @@ -70,6 +72,7 @@ function handleUpdate( publicKeyPublished: PublicKeyUpdated.Event, errors: EventSourcingError[], ) { + logger.trace({ event: publicKeyPublished }, "Handling public_key_updated event"); let publicKeyBase64 = keyByOrganization.get(publicKeyPublished.organization); if (publicKeyBase64 === undefined) { errors.push( diff --git a/api/src/service/domain/organization/public_key_get.spec.ts b/api/src/service/domain/organization/public_key_get.spec.ts index 265fd6448..720f1cb14 100644 --- a/api/src/service/domain/organization/public_key_get.spec.ts +++ b/api/src/service/domain/organization/public_key_get.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotFound } from "../errors/not_found"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/organization/public_key_get.ts b/api/src/service/domain/organization/public_key_get.ts index 63a9183cf..3782518ee 100644 --- a/api/src/service/domain/organization/public_key_get.ts +++ b/api/src/service/domain/organization/public_key_get.ts @@ -1,10 +1,11 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; -import { KeysByOrganization, sourcePublicKeys } from "./public_key_eventsourcing"; -import { Organization, PublicKeyBase64 } from "./public_key"; import { NotFound } from "../errors/not_found"; +import { Organization, PublicKeyBase64 } from "./public_key"; +import { KeysByOrganization, sourcePublicKeys } from "./public_key_eventsourcing"; interface Repository { getPublicKeysEvents(): Promise>; @@ -34,11 +35,13 @@ export async function getPublicKey( ): Promise> { // No permission checked since every user should be able // to list all public keys + logger.trace("Fetching public key..."); const keysByOrganization = await getAllPublicKeys(ctx, repository); if (Result.isErr(keysByOrganization)) { return new VError(keysByOrganization, "get all public keys failed"); } + const publicKey = keysByOrganization.get(organization); if (!publicKey) { return new VError( @@ -56,7 +59,7 @@ export async function publicKeyAlreadyExists( ): Promise> { // No permission checked since every user should be able // to list all public keys - + logger.trace({ organization }, "Checking if public key already exists"); const keysByOrganization = await getAllPublicKeys(ctx, repository); if (Result.isErr(keysByOrganization)) { return new VError(keysByOrganization, "get all public keys failed"); diff --git a/api/src/service/domain/organization/public_key_publish.spec.ts b/api/src/service/domain/organization/public_key_publish.spec.ts index c78042969..ec11f7e39 100644 --- a/api/src/service/domain/organization/public_key_publish.spec.ts +++ b/api/src/service/domain/organization/public_key_publish.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "./service_user"; import { publishPublicKey } from "./public_key_publish"; diff --git a/api/src/service/domain/organization/public_key_publish.ts b/api/src/service/domain/organization/public_key_publish.ts index 37548fd6a..a327495b6 100644 --- a/api/src/service/domain/organization/public_key_publish.ts +++ b/api/src/service/domain/organization/public_key_publish.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { AlreadyExists } from "../errors/already_exists"; @@ -10,6 +10,7 @@ import { Organization, PublicKeyBase64 } from "./public_key"; import { sourcePublicKeys } from "./public_key_eventsourcing"; import * as PublicKeyPublished from "./public_key_published"; import { ServiceUser } from "./service_user"; +import logger from "lib/logger"; export interface RequestData { organization: Organization; @@ -46,15 +47,17 @@ export async function publishPublicKey( organization, publicKeyBase64, ); + + logger.trace({ createEvent }, "event to publish public key created"); if (Result.isErr(createEvent)) { return new VError(createEvent, "failed to create publish public key event"); } - // Check if public key already exists const publicKeyExistsResult = await repository.publicKeyAlreadyExists(organization); if (Result.isErr(publicKeyExistsResult)) { return new VError(publicKeyExistsResult, "public key exists check failed"); } + const publicKeyExists = publicKeyExistsResult; if (publicKeyExists) { return new AlreadyExists(ctx, createEvent, createEvent.publicKey); diff --git a/api/src/service/domain/organization/public_key_published.ts b/api/src/service/domain/organization/public_key_published.ts index 0d2436f6d..f9e7197ac 100644 --- a/api/src/service/domain/organization/public_key_published.ts +++ b/api/src/service/domain/organization/public_key_published.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "./identity"; @@ -40,6 +40,8 @@ export function createEvent( publicKey, time, }; + logger.trace("Creating public_key_publish event..."); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/organization/public_key_update.spec.ts b/api/src/service/domain/organization/public_key_update.spec.ts index e76a20a46..e6f70f0c8 100644 --- a/api/src/service/domain/organization/public_key_update.spec.ts +++ b/api/src/service/domain/organization/public_key_update.spec.ts @@ -1,5 +1,5 @@ import { assert, expect } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotFound } from "../errors/not_found"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/organization/public_key_update.ts b/api/src/service/domain/organization/public_key_update.ts index 110d7691f..19e966d8e 100644 --- a/api/src/service/domain/organization/public_key_update.ts +++ b/api/src/service/domain/organization/public_key_update.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -9,6 +9,7 @@ import { Organization, PublicKeyBase64 } from "./public_key"; import { sourcePublicKeys } from "./public_key_eventsourcing"; import * as PublicKeyPublished from "./public_key_published"; import { ServiceUser } from "./service_user"; +import logger from "lib/logger"; export interface RequestData { organization: Organization; @@ -36,11 +37,13 @@ export async function updatePublicKey( repository: Repository, ): Promise> { const { organization, publicKey } = requestData; - // Check if public key exists + + logger.trace("Checking if public key already exists"); const publicKeyBase64Result = await repository.getPublicKey(organization); if (Result.isErr(publicKeyBase64Result)) { return new VError(publicKeyBase64Result, "couldn't get public key"); } + if (publicKey === publicKeyBase64Result) { return new Error(`the same public key is already stored for ${organization}`); } diff --git a/api/src/service/domain/organization/public_key_updated.ts b/api/src/service/domain/organization/public_key_updated.ts index 8f8793ad4..fdec150f0 100644 --- a/api/src/service/domain/organization/public_key_updated.ts +++ b/api/src/service/domain/organization/public_key_updated.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "./identity"; @@ -32,6 +32,8 @@ export function createEvent( publicKey: string, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating public_key_update event..."); + const event = { type: eventType, source, diff --git a/api/src/service/domain/organization/service_user.ts b/api/src/service/domain/organization/service_user.ts index 4f76c143c..ec2411ca1 100644 --- a/api/src/service/domain/organization/service_user.ts +++ b/api/src/service/domain/organization/service_user.ts @@ -3,7 +3,3 @@ export interface ServiceUser { groups: string[]; address: string; } - -export function userIdentities({ id, groups }: ServiceUser): string[] { - return [id].concat(groups); -} diff --git a/api/src/service/domain/organization/user_create.spec.ts b/api/src/service/domain/organization/user_create.spec.ts index bfc273d58..96c522976 100644 --- a/api/src/service/domain/organization/user_create.spec.ts +++ b/api/src/service/domain/organization/user_create.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { AlreadyExists } from "../errors/already_exists"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/organization/user_create.ts b/api/src/service/domain/organization/user_create.ts index b66a062d8..6d399906b 100644 --- a/api/src/service/domain/organization/user_create.ts +++ b/api/src/service/domain/organization/user_create.ts @@ -1,8 +1,9 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import { userDefaultIntents, userIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { BusinessEvent } from "../business_event"; @@ -69,6 +70,7 @@ export async function createUser( }, {}), additionalData: data.additionalData || {}, }; + logger.trace("Creating user ", data); const createEvent = UserCreated.createEvent(source, publisher, eventTemplate); if (Result.isErr(createEvent)) { @@ -121,7 +123,10 @@ export async function createUser( } } + logger.trace("User creation is legit - setting-up user account!"); + eventTemplate.passwordHash = await repository.hash(data.passwordPlaintext); + logger.trace("Creating key-pair for new user..."); // Every user gets her own address: const keyPair = await repository.createKeyPair(); @@ -133,6 +138,7 @@ export async function createUser( if (Result.isErr(result)) { return new InvalidCommand(ctx, createEvent, [result]); } + logger.trace("Granting default permissions to new user ..."); // Create events that'll grant default permissions to the user: const defaultPermissionGrantedEvents: Result.Type = []; diff --git a/api/src/service/domain/organization/user_created.ts b/api/src/service/domain/organization/user_created.ts index 2dde994b1..6b7001ad9 100644 --- a/api/src/service/domain/organization/user_created.ts +++ b/api/src/service/domain/organization/user_created.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; - -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -45,12 +45,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), user: initialDataSchema.required(), }); @@ -61,6 +57,8 @@ export function createEvent( user: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating user_create event..."); + const event = { type: eventType, source, @@ -98,6 +96,6 @@ export function createFrom(ctx: Ctx, event: Event): Result.Type new EventSourcingError({ ctx, event, target: user }, error), + (error) => new EventSourcingError({ ctx, event, target: user }, error), ); } diff --git a/api/src/service/domain/organization/user_disable.spec.ts b/api/src/service/domain/organization/user_disable.spec.ts index d34140589..0b1adbffe 100644 --- a/api/src/service/domain/organization/user_disable.spec.ts +++ b/api/src/service/domain/organization/user_disable.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { PreconditionError } from "../errors/precondition_error"; diff --git a/api/src/service/domain/organization/user_disable.ts b/api/src/service/domain/organization/user_disable.ts index 5edf89eaa..a627ce4ad 100644 --- a/api/src/service/domain/organization/user_disable.ts +++ b/api/src/service/domain/organization/user_disable.ts @@ -2,7 +2,7 @@ import Joi = require("joi"); import { VError } from "verror"; import isEqual = require("lodash.isequal"); import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import * as UserRecord from "./user_record"; import * as GlobalPermissions from "../workflow/global_permissions"; import * as UserAssignments from "../workflow/user_assignments"; import * as UserAssignmentsGet from "../workflow/user_assignments_get"; +import logger from "lib/logger"; export interface RequestData { userId: string; @@ -82,7 +83,7 @@ export async function disableUser( }); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const isAuthorized = GlobalPermissions.permits(globalPermissions, issuer, [intent]); if (!isAuthorized) { diff --git a/api/src/service/domain/organization/user_disabled.ts b/api/src/service/domain/organization/user_disabled.ts index 4443d8d0c..02eabdf7a 100644 --- a/api/src/service/domain/organization/user_disabled.ts +++ b/api/src/service/domain/organization/user_disabled.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import * as UserRecord from "../organization/user_record"; import { Identity } from "./identity"; @@ -38,6 +38,8 @@ export function createEvent( user: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating user_disable event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/organization/user_enable.spec.ts b/api/src/service/domain/organization/user_enable.spec.ts index c546ce700..638d20b0e 100644 --- a/api/src/service/domain/organization/user_enable.spec.ts +++ b/api/src/service/domain/organization/user_enable.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/organization/user_enable.ts b/api/src/service/domain/organization/user_enable.ts index 455f1afbc..23e530d4c 100644 --- a/api/src/service/domain/organization/user_enable.ts +++ b/api/src/service/domain/organization/user_enable.ts @@ -2,7 +2,7 @@ import Joi = require("joi"); import { VError } from "verror"; import isEqual = require("lodash.isequal"); import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import * as UserEventSourcing from "./user_eventsourcing"; import * as UserEnabled from "./user_enabled"; import * as UserRecord from "./user_record"; import * as GlobalPermissions from "../workflow/global_permissions"; +import logger from "lib/logger"; export interface RequestData { userId: string; @@ -50,6 +51,7 @@ export async function enableUser( const globalPermissions = globalPermissionsResult; // Create the new event: + logger.trace("Creating userEnabled event"); const userEnabled = UserEnabled.createEvent(source, publisher, { id: data.userId, }); @@ -77,7 +79,7 @@ export async function enableUser( }); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const isAuthorized = GlobalPermissions.permits(globalPermissions, issuer, [intent]); if (!isAuthorized) { diff --git a/api/src/service/domain/organization/user_enabled.ts b/api/src/service/domain/organization/user_enabled.ts index d7976d21e..a12fab30a 100644 --- a/api/src/service/domain/organization/user_enabled.ts +++ b/api/src/service/domain/organization/user_enabled.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import * as UserRecord from "../organization/user_record"; import { Identity } from "./identity"; @@ -38,6 +38,8 @@ export function createEvent( user: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating user_disable event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/organization/user_eventsourcing.ts b/api/src/service/domain/organization/user_eventsourcing.ts index 742d1421c..6d4771ebb 100644 --- a/api/src/service/domain/organization/user_eventsourcing.ts +++ b/api/src/service/domain/organization/user_eventsourcing.ts @@ -1,13 +1,13 @@ +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; +import logger from "lib/logger"; import { VError } from "verror"; - -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; import * as UserCreated from "./user_created"; -import * as UserEnabled from "./user_enabled"; import * as UserDisabled from "./user_disabled"; +import * as UserEnabled from "./user_enabled"; import * as UserPasswordChanged from "./user_password_changed"; import * as UserPermissionGranted from "./user_permission_granted"; import * as UserPermissionRevoked from "./user_permission_revoked"; @@ -30,6 +30,7 @@ export function sourceUserRecords( continue; } + logger.trace({ event }, "sourcing user related event"); const user = sourceEvent(ctx, event, users); if (Result.isErr(user)) { errors.push(user); @@ -63,6 +64,7 @@ function sourceEvent( if (Result.isOk(userId)) { // The event refers to an existing user, so // the user should have been initialized already. + logger.trace("Sourcing user event ..."); user = get(users, userId); if (Result.isErr(user)) { @@ -95,6 +97,8 @@ function get( users: Map, userId: UserRecord.Id, ): Result.Type { + logger.trace("Fetching user: ", userId); + const user = users.get(userId); if (user === undefined) { return new VError(`user ${userId} not yet initialized`); @@ -143,6 +147,7 @@ export function newUserFromEvent( user: UserRecord.UserRecord, event: BusinessEvent, ): Result.Type { + logger.trace({ event }, "Creating new use from event"); const eventModule = getEventModule(event); if (Result.isErr(eventModule)) { return eventModule; diff --git a/api/src/service/domain/organization/user_get.ts b/api/src/service/domain/organization/user_get.ts index 1db0169b9..edbcb8ae6 100644 --- a/api/src/service/domain/organization/user_get.ts +++ b/api/src/service/domain/organization/user_get.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotFound } from "../errors/not_found"; @@ -16,6 +17,11 @@ export async function getOneUser( userId: string, repository: Repository, ): Promise> { + logger.trace( + "Fetching user by id. *NOTE* errors are ignored in this procedure. UserId is:", + userId, + ); + const allEvents = await repository.getUserEvents(); // Errors are ignored here: @@ -37,6 +43,7 @@ export async function getAllUsers( serviceUser: ServiceUser, repository: Repository, ): Promise> { + logger.trace("Fetching all users. *NOTE* errors are ignored in this procedure."); const allEvents = await repository.getUserEvents(); // Errors are ignored here: const { users } = sourceUserRecords(ctx, allEvents); diff --git a/api/src/service/domain/organization/user_password_change.spec.ts b/api/src/service/domain/organization/user_password_change.spec.ts index 4372f9a36..6c03e201e 100644 --- a/api/src/service/domain/organization/user_password_change.spec.ts +++ b/api/src/service/domain/organization/user_password_change.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { hashPassword, isPasswordMatch } from "../../password"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/organization/user_password_change.ts b/api/src/service/domain/organization/user_password_change.ts index 4bcb50265..27fed053d 100644 --- a/api/src/service/domain/organization/user_password_change.ts +++ b/api/src/service/domain/organization/user_password_change.ts @@ -2,7 +2,7 @@ import Joi = require("joi"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -12,6 +12,8 @@ import { ServiceUser } from "./service_user"; import * as UserEventSourcing from "./user_eventsourcing"; import * as UserPasswordChanged from "./user_password_changed"; import * as UserRecord from "./user_record"; +import { safePasswordSchema } from "lib/joiValidation"; +import logger from "lib/logger"; export interface RequestData { userId: string; @@ -20,11 +22,7 @@ export interface RequestData { const requestDataSchema = Joi.object({ userId: UserRecord.idSchema.required(), - newPassword: Joi.string() - .min(8) - .regex(/[a-zA-z]/) - .regex(/[0-9]/) - .required(), + newPassword: safePasswordSchema.required(), }); export function validate(input: any): Result.Type { @@ -52,9 +50,12 @@ export async function changeUserPassword( id: data.userId, passwordHash: await repository.hash(data.newPassword), }); + + logger.trace({ event: passwordChanged }, "Checking validity of password changed event"); if (Result.isErr(passwordChanged)) { return new VError(passwordChanged, "failed to create user password changed event"); } + if (Result.isErr(validationResult)) { return new PreconditionError(ctx, passwordChanged, validationResult.message); } @@ -65,12 +66,12 @@ export async function changeUserPassword( } const user = userResult; - // Check if revokee and issuer belong to the same organization + logger.trace({ issuer }, "Checking if issuer and revokee belong to the same organization"); if (userResult.organization !== issuerOrganization) { return new NotAuthorized({ ctx, userId: issuer.id, intent }); } - // Check authorization (if not root): + logger.trace("Check if user is root"); if (issuer.id !== "root") { const isAuthorized = UserRecord.permits(user, issuer, [intent]); if (!isAuthorized) { diff --git a/api/src/service/domain/organization/user_password_changed.ts b/api/src/service/domain/organization/user_password_changed.ts index bb5742792..66a431e2f 100644 --- a/api/src/service/domain/organization/user_password_changed.ts +++ b/api/src/service/domain/organization/user_password_changed.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import * as UserRecord from "../organization/user_record"; import { Identity } from "./identity"; @@ -47,6 +47,8 @@ export function createEvent( time, user, }; + logger.trace("Creating user_password_changed event"); + const validationResult = validate(event); if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); diff --git a/api/src/service/domain/organization/user_permission_grant.spec.ts b/api/src/service/domain/organization/user_permission_grant.spec.ts index 0917fa209..bfa0a7997 100644 --- a/api/src/service/domain/organization/user_permission_grant.spec.ts +++ b/api/src/service/domain/organization/user_permission_grant.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/organization/user_permission_grant.ts b/api/src/service/domain/organization/user_permission_grant.ts index bf988fad7..aa1ce4bb6 100644 --- a/api/src/service/domain/organization/user_permission_grant.ts +++ b/api/src/service/domain/organization/user_permission_grant.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import { ServiceUser } from "./service_user"; import * as UserEventSourcing from "./user_eventsourcing"; import * as UserPermissionGranted from "./user_permission_granted"; import * as UserRecord from "./user_record"; +import logger from "lib/logger"; interface Repository { getTargetUser(userId: UserRecord.Id): Promise>; @@ -35,7 +36,6 @@ export async function grantUserPermission( return new NotFound(ctx, "user", userId); } - // Create the new event: const permissionGranted = UserPermissionGranted.createEvent( ctx.source, issuer.id, @@ -47,7 +47,7 @@ export async function grantUserPermission( return new VError(permissionGranted, "failed to create user permission granted event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const grantIntent: Intent = "user.intent.grantPermission"; if (!UserRecord.permits(user, issuer, [grantIntent])) { @@ -55,7 +55,7 @@ export async function grantUserPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionGranted }, "Checking validity of event"); const updatedUser = UserEventSourcing.newUserFromEvent(ctx, user, permissionGranted); if (Result.isErr(updatedUser)) { return new InvalidCommand(ctx, permissionGranted, [updatedUser]); @@ -64,7 +64,7 @@ export async function grantUserPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(user.permissions, updatedUser.permissions)) { return []; - } else { - return [permissionGranted]; } + + return [permissionGranted]; } diff --git a/api/src/service/domain/organization/user_permission_granted.ts b/api/src/service/domain/organization/user_permission_granted.ts index 6901aadfa..86094f5e2 100644 --- a/api/src/service/domain/organization/user_permission_granted.ts +++ b/api/src/service/domain/organization/user_permission_granted.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import Intent, { userIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "./identity"; @@ -37,6 +37,8 @@ export function createEvent( grantee: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating user_permission_granted event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/organization/user_permission_revoke.spec.ts b/api/src/service/domain/organization/user_permission_revoke.spec.ts index 671799e29..67443aa36 100644 --- a/api/src/service/domain/organization/user_permission_revoke.spec.ts +++ b/api/src/service/domain/organization/user_permission_revoke.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/organization/user_permission_revoke.ts b/api/src/service/domain/organization/user_permission_revoke.ts index ba2c53804..898d57c92 100644 --- a/api/src/service/domain/organization/user_permission_revoke.ts +++ b/api/src/service/domain/organization/user_permission_revoke.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import { ServiceUser } from "../organization/service_user"; import * as UserEventSourcing from "./user_eventsourcing"; import * as UserPermissionRevoked from "./user_permission_revoked"; import * as UserRecord from "./user_record"; +import logger from "lib/logger"; interface Repository { getTargetUser(userId: UserRecord.Id): Promise>; @@ -44,7 +45,7 @@ export async function revokeUserPermission( return new VError(permissionRevoked, "failed to create permission revoked event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const revokeIntent = "user.intent.revokePermission"; if (!UserRecord.permits(user, issuer, [revokeIntent])) { @@ -52,7 +53,7 @@ export async function revokeUserPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionRevoked }, "Checking event validity"); const updatedUser = UserEventSourcing.newUserFromEvent(ctx, user, permissionRevoked); if (Result.isErr(updatedUser)) { return new InvalidCommand(ctx, permissionRevoked, [updatedUser]); @@ -61,7 +62,7 @@ export async function revokeUserPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(user.permissions, updatedUser.permissions)) { return []; - } else { - return [permissionRevoked]; } + + return [permissionRevoked]; } diff --git a/api/src/service/domain/organization/user_permission_revoked.ts b/api/src/service/domain/organization/user_permission_revoked.ts index 964257eed..4d43c461a 100644 --- a/api/src/service/domain/organization/user_permission_revoked.ts +++ b/api/src/service/domain/organization/user_permission_revoked.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import Intent, { userIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -37,6 +37,8 @@ export function createEvent( revokee: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating user_permission_reviked event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/system_information/provisioning_end.ts b/api/src/service/domain/system_information/provisioning_end.ts index 5d54bf01e..a04ceb737 100644 --- a/api/src/service/domain/system_information/provisioning_end.ts +++ b/api/src/service/domain/system_information/provisioning_end.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -7,25 +7,25 @@ import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; import * as ProvisioningEnded from "./provisioning_ended"; import { sourceSystemInformation } from "./system_information_eventsourcing"; +import logger from "lib/logger"; export async function setProvisioningEndFlag( ctx: Ctx, issuer: ServiceUser, ): Promise> { - // Check authorization (only root): + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { const intent = "provisioning.end"; return new NotAuthorized({ ctx, userId: issuer.id, intent }); } - // Create the new event: const provisioningEndedEventResult = ProvisioningEnded.createEvent(ctx.source, issuer.id); if (Result.isErr(provisioningEndedEventResult)) { return new VError(provisioningEndedEventResult, "failed to create event"); } const provisioningEndedEvent = provisioningEndedEventResult; - // Check that the event is valid by trying to "apply" it: + logger.trace({ event: provisioningEndedEvent }, "Checking if event is valid"); const { errors } = sourceSystemInformation(ctx, [provisioningEndedEvent]); if (errors.length > 0) { return new InvalidCommand(ctx, provisioningEndedEvent, errors); diff --git a/api/src/service/domain/system_information/provisioning_ended.ts b/api/src/service/domain/system_information/provisioning_ended.ts index 6a92935f2..4a2001817 100644 --- a/api/src/service/domain/system_information/provisioning_ended.ts +++ b/api/src/service/domain/system_information/provisioning_ended.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -25,6 +26,7 @@ export function createEvent( publisher: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating provisioning_end event"); const event = { type: eventType, source, diff --git a/api/src/service/domain/system_information/provisioning_get.ts b/api/src/service/domain/system_information/provisioning_get.ts index 700754026..7309c8d3e 100644 --- a/api/src/service/domain/system_information/provisioning_get.ts +++ b/api/src/service/domain/system_information/provisioning_get.ts @@ -1,12 +1,13 @@ import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; import * as SystemInformation from "./system_information"; import { sourceSystemInformation } from "./system_information_eventsourcing"; +import logger from "lib/logger"; interface Repository { getSystemInformationEvents(): Promise>; @@ -17,7 +18,7 @@ export async function getProvisionStatus( user: ServiceUser, repository: Repository, ): Promise> { - // Check authorization (only root): + logger.trace({ user }, "Checking if user is root"); if (user.id !== "root") { const intent: Intent = "provisioning.get"; return new NotAuthorized({ @@ -26,14 +27,18 @@ export async function getProvisionStatus( intent, }); } + const systemInformationEventsResult = await repository.getSystemInformationEvents(); if (Result.isErr(systemInformationEventsResult)) { return new VError(systemInformationEventsResult, "failed to get system information events"); } + const { systemInformation, errors } = sourceSystemInformation(ctx, systemInformationEventsResult); + // Only return first error if there are any if (errors.length > 0) { return errors[0]; } + return systemInformation.provisioningStatus; } diff --git a/api/src/service/domain/system_information/provisioning_start.ts b/api/src/service/domain/system_information/provisioning_start.ts index 6559bbc53..cc4c553ab 100644 --- a/api/src/service/domain/system_information/provisioning_start.ts +++ b/api/src/service/domain/system_information/provisioning_start.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -7,22 +7,23 @@ import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; import * as ProvisioningStarted from "./provisioning_started"; import { sourceSystemInformation } from "./system_information_eventsourcing"; +import logger from "lib/logger"; export async function setProvisioningStartFlag( ctx: Ctx, issuer: ServiceUser, ): Promise> { - // Check authorization (only root): + logger.trace("Checking if user is root"); if (issuer.id !== "root") { const intent = "provisioning.start"; return new NotAuthorized({ ctx, userId: issuer.id, intent }); } - // Create the new event: const provisioningStartedEventResult = ProvisioningStarted.createEvent(ctx.source, issuer.id); if (Result.isErr(provisioningStartedEventResult)) { return new VError(provisioningStartedEventResult, "failed to create event"); } + const provisioningStartedEvent = provisioningStartedEventResult; // Check that the event is valid by trying to "apply" it: diff --git a/api/src/service/domain/system_information/provisioning_started.ts b/api/src/service/domain/system_information/provisioning_started.ts index 9f23ac6ba..59d8d6e92 100644 --- a/api/src/service/domain/system_information/provisioning_started.ts +++ b/api/src/service/domain/system_information/provisioning_started.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -25,6 +26,8 @@ export function createEvent( publisher: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating provisioning_start event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/system_information/system_information_eventsourcing.ts b/api/src/service/domain/system_information/system_information_eventsourcing.ts index 94960fff9..ec0dde9c2 100644 --- a/api/src/service/domain/system_information/system_information_eventsourcing.ts +++ b/api/src/service/domain/system_information/system_information_eventsourcing.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -21,6 +22,7 @@ export function sourceSystemInformation( }; const errors: EventSourcingError[] = []; for (const event of events) { + logger.trace({ event }, "Applying system information event"); apply(ctx, systemInformation, event, errors); } return { systemInformation, errors }; diff --git a/api/src/service/domain/workflow/global_permission_grant.ts b/api/src/service/domain/workflow/global_permission_grant.ts index 7f048105b..ed9f5898f 100644 --- a/api/src/service/domain/workflow/global_permission_grant.ts +++ b/api/src/service/domain/workflow/global_permission_grant.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; @@ -10,6 +10,7 @@ import { ServiceUser } from "../organization/service_user"; import * as UserRecord from "../organization/user_record"; import * as GlobalPermissions from "./global_permissions"; import * as GlobalPermissionGranted from "./global_permission_granted"; +import logger from "lib/logger"; interface Repository { getGlobalPermissions(): Promise>; @@ -25,7 +26,6 @@ export async function grantGlobalPermission( intent: Intent, repository: Repository, ): Promise> { - // Create the new event: const globalPermissionGranted = GlobalPermissionGranted.createEvent( ctx.source, issuer.id, @@ -35,14 +35,16 @@ export async function grantGlobalPermission( if (Result.isErr(globalPermissionGranted)) { return new VError(globalPermissionGranted, "failed to create global permission granted event"); } + const grantIntent = "global.grantPermission"; const globalPermissionsResult = await repository.getGlobalPermissions(); + if (Result.isErr(globalPermissionsResult)) { return new VError(globalPermissionsResult, "get global permissions failed"); } const currentGlobalPermissions = globalPermissionsResult; - // Check if grantee is group + logger.trace({ grantee }, "Checking if grantee is a group"); const isGroupResult = await repository.isGroup(grantee); if (Result.isErr(isGroupResult)) { return new VError(isGroupResult, "isGroup check failed"); @@ -56,24 +58,27 @@ export async function grantGlobalPermission( globalPermissionGranted, "Cannot assign global permissions to groups", ); - } else { - // If the grantee is not a group, he/she is a user - const userResult = await repository.getUser(grantee); - if (Result.isErr(userResult)) { - return new PreconditionError(ctx, globalPermissionGranted, userResult.message); - } - // Check if grantee and issuer belong to the same organization - if (userResult.organization !== issuerOrganization) { - return new NotAuthorized({ - ctx, - userId: issuer.id, - intent: grantIntent, - target: currentGlobalPermissions, - }); - } } - // Check authorization (if not root): + const userResult = await repository.getUser(grantee); + if (Result.isErr(userResult)) { + return new PreconditionError(ctx, globalPermissionGranted, userResult.message); + } + + logger.trace( + { grantee, organization: issuerOrganization }, + "Checking if grantee and user belong to the same organization", + ); + if (userResult.organization !== issuerOrganization) { + return new NotAuthorized({ + ctx, + userId: issuer.id, + intent: grantIntent, + target: currentGlobalPermissions, + }); + } + + logger.trace({ issuer }, "Checking if issuer is authorized"); if (issuer.id !== "root") { if (!GlobalPermissions.permits(currentGlobalPermissions, issuer, [grantIntent])) { return new NotAuthorized({ diff --git a/api/src/service/domain/workflow/global_permission_granted.ts b/api/src/service/domain/workflow/global_permission_granted.ts index a31631c3e..592780d15 100644 --- a/api/src/service/domain/workflow/global_permission_granted.ts +++ b/api/src/service/domain/workflow/global_permission_granted.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { globalIntents } from "../../../authz/intents"; @@ -19,12 +20,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), permission: Joi.valid(globalIntents).required(), grantee: Joi.string().required(), @@ -37,6 +34,7 @@ export function createEvent( grantee: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace({ grantee, permission }, "Creating global permission granted event"); const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/global_permission_revoke.ts b/api/src/service/domain/workflow/global_permission_revoke.ts index b1f832667..24af12127 100644 --- a/api/src/service/domain/workflow/global_permission_revoke.ts +++ b/api/src/service/domain/workflow/global_permission_revoke.ts @@ -1,6 +1,7 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; @@ -32,9 +33,11 @@ export async function revokeGlobalPermission( intent, revokee, ); + if (Result.isErr(globalPermissionRevoked)) { return new VError(globalPermissionRevoked, "failed to create global permission revoked event"); } + const revokeIntent = "global.revokePermission"; const currentGlobalPermissionsResult = await repository.getGlobalPermissions(); if (Result.isErr(currentGlobalPermissionsResult)) { @@ -42,38 +45,41 @@ export async function revokeGlobalPermission( } const currentGlobalPermissions = currentGlobalPermissionsResult; - // Check if revokee is group const isGroupResult = await repository.isGroup(revokee); if (Result.isErr(isGroupResult)) { return new VError(isGroupResult, "isGroup check failed"); } const isGroup = isGroupResult; - // If revokee is group, return an error because global permissions cannot be granted to groups + logger.trace({ revokee }, "Checking if revokee is a group"); if (isGroup) { return new PreconditionError( ctx, globalPermissionRevoked, "Cannot assign global permissions to groups", ); - } else { - // If the revokee is not a group, he/she is a user - const userResult = await repository.getUser(revokee); - if (Result.isErr(userResult)) { - return new PreconditionError(ctx, globalPermissionRevoked, userResult.message); - } - // Check if revokee and issuer belong to the same organization - if (userResult.organization !== issuerOrganization) { - return new NotAuthorized({ - ctx, - userId: issuer.id, - intent: revokeIntent, - target: currentGlobalPermissions, - }); - } } - // Check authorization (if not root): + // If the revokee is not a group, he/she is a user + const userResult = await repository.getUser(revokee); + if (Result.isErr(userResult)) { + return new PreconditionError(ctx, globalPermissionRevoked, userResult.message); + } + + logger.trace( + { revokee: userResult, organization: issuerOrganization }, + "Checking if revokee and issuer belong to the same organization", + ); + if (userResult.organization !== issuerOrganization) { + return new NotAuthorized({ + ctx, + userId: issuer.id, + intent: revokeIntent, + target: currentGlobalPermissions, + }); + } + + logger.trace({ issuer }, "Checking if user is root"); if (issuer.id !== "root") { if (!GlobalPermissions.permits(currentGlobalPermissions, issuer, [revokeIntent])) { return new NotAuthorized({ diff --git a/api/src/service/domain/workflow/global_permission_revoked.ts b/api/src/service/domain/workflow/global_permission_revoked.ts index 004845b76..244e7cbf4 100644 --- a/api/src/service/domain/workflow/global_permission_revoked.ts +++ b/api/src/service/domain/workflow/global_permission_revoked.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { globalIntents } from "../../../authz/intents"; @@ -19,12 +20,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), permission: Joi.valid(globalIntents).required(), revokee: Joi.string().required(), @@ -36,7 +33,8 @@ export function createEvent( permission: Intent, revokee: Identity, time: string = new Date().toISOString(), -): Result.Type { +): Result.Type { + logger.trace({ revokee, permission, publisher }, "Creating event from request"); const event = { type: eventType, source, @@ -45,7 +43,9 @@ export function createEvent( permission, revokee, }; + const validationResult = validate(event); + if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); } diff --git a/api/src/service/domain/workflow/global_permissions.ts b/api/src/service/domain/workflow/global_permissions.ts index 9f76cd998..c858e07a2 100644 --- a/api/src/service/domain/workflow/global_permissions.ts +++ b/api/src/service/domain/workflow/global_permissions.ts @@ -19,9 +19,7 @@ export interface GlobalPermissions { const schema = Joi.object({ permissions: Permissions.permissionsSchema.required(), - log: Joi.array() - .required() - .items(globalPermissionsTraceEventSchema), + log: Joi.array().required().items(globalPermissionsTraceEventSchema), additionalData: AdditionalData.schema, }); @@ -46,8 +44,10 @@ export function permits( const eligibles = globalPermissions.permissions[intent] || []; return acc.concat(eligibles); }, []); - const hasPermission = eligibleIdentities.some(identity => + + const hasPermission = eligibleIdentities.some((identity) => canAssumeIdentity(actingUser, identity), ); + return hasPermission; } diff --git a/api/src/service/domain/workflow/global_permissions_eventsourcing.ts b/api/src/service/domain/workflow/global_permissions_eventsourcing.ts index 0f68415fb..9e26528e5 100644 --- a/api/src/service/domain/workflow/global_permissions_eventsourcing.ts +++ b/api/src/service/domain/workflow/global_permissions_eventsourcing.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -6,6 +6,7 @@ import * as GlobalPermissionGranted from "./global_permission_granted"; import * as GlobalPermissionRevoked from "./global_permission_revoked"; import * as GlobalPermissions from "./global_permissions"; import { GlobalPermissionsTraceEvent } from "./global_permissions_trace_event"; +import logger from "lib/logger"; export function sourceGlobalPermissions( ctx: Ctx, @@ -39,10 +40,13 @@ function applyGrantPermission( permissionGranted: GlobalPermissionGranted.Event, errors: EventSourcingError[], ) { + logger.trace({ event: permissionGranted }, "Applying grant permission event"); + const eligibleIdentities = globalPerms.permissions[permissionGranted.permission] || []; if (!eligibleIdentities.includes(permissionGranted.grantee)) { eligibleIdentities.push(permissionGranted.grantee); } + globalPerms.permissions[permissionGranted.permission] = eligibleIdentities; const result = GlobalPermissions.validate(globalPerms); @@ -65,8 +69,7 @@ function applyRevokePermission( permissionRevoked: GlobalPermissionRevoked.Event, errors: EventSourcingError[], ) { - const permissionsObject = globalPerms.permissions; - + logger.trace({ event: permissionRevoked }, "Applying permission revoked event"); const eligibleIdentities = globalPerms.permissions[permissionRevoked.permission]; if (eligibleIdentities !== undefined) { const foundIndex = eligibleIdentities.indexOf(permissionRevoked.revokee); diff --git a/api/src/service/domain/workflow/global_permissions_get.ts b/api/src/service/domain/workflow/global_permissions_get.ts index 3f7448ddf..76f2f6bf4 100644 --- a/api/src/service/domain/workflow/global_permissions_get.ts +++ b/api/src/service/domain/workflow/global_permissions_get.ts @@ -1,5 +1,6 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import { BusinessEvent } from "../business_event"; import { canAssumeIdentity } from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; @@ -28,7 +29,9 @@ export async function getGlobalPermissions( function filterPermissions( globalPermissions: GlobalPermissions.GlobalPermissions, user: ServiceUser, -) { +): void { + logger.trace("Flitering permissions for: ", user); + if (user.id === "root") { // root always sees all permissions return; @@ -48,8 +51,9 @@ function filterPermissions( const uservVisibleIdentities = identitiesAuthorizedFor( globalPermissions, intent as Intent, - ).filter(identity => canAssumeIdentity(user, identity)); + ).filter((identity) => canAssumeIdentity(user, identity)); userVisiblePermissions[intent] = uservVisibleIdentities; } + globalPermissions.permissions = userVisiblePermissions; } diff --git a/api/src/service/domain/workflow/global_permissions_grant.spec.ts b/api/src/service/domain/workflow/global_permissions_grant.spec.ts index f7d3604ba..d985de71c 100644 --- a/api/src/service/domain/workflow/global_permissions_grant.spec.ts +++ b/api/src/service/domain/workflow/global_permissions_grant.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { Identity } from "../organization/identity"; diff --git a/api/src/service/domain/workflow/global_permissions_revoke.spec.ts b/api/src/service/domain/workflow/global_permissions_revoke.spec.ts index 998aa3339..5308b0d7e 100644 --- a/api/src/service/domain/workflow/global_permissions_revoke.spec.ts +++ b/api/src/service/domain/workflow/global_permissions_revoke.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { Identity } from "../organization/identity"; diff --git a/api/src/service/domain/workflow/notification_created.ts b/api/src/service/domain/workflow/notification_created.ts index 618bddef2..34883be01 100644 --- a/api/src/service/domain/workflow/notification_created.ts +++ b/api/src/service/domain/workflow/notification_created.ts @@ -1,4 +1,5 @@ import Joi = require("joi"); +import logger from "lib/logger"; import uuid = require("uuid"); import { VError } from "verror"; @@ -29,12 +30,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), notificationId: Notification.idSchema.required(), recipient: UserRecord.idSchema, @@ -55,6 +52,7 @@ export function createEvent( workflowitemId?: Workflowitem.Id, time: string = new Date().toISOString(), ): Result.Type { + logger.trace({ recipient, publisher, businessEvent }, "Creating notification created event"); const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/notification_eventsourcing.ts b/api/src/service/domain/workflow/notification_eventsourcing.ts index 471520fe2..a87491544 100644 --- a/api/src/service/domain/workflow/notification_eventsourcing.ts +++ b/api/src/service/domain/workflow/notification_eventsourcing.ts @@ -1,5 +1,6 @@ -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -17,9 +18,11 @@ export function sourceNotifications( ): { notificationsById: NotificationsById; errors: EventSourcingError[] } { const notificationsById = new Map(); const errors: EventSourcingError[] = []; + for (const event of events) { apply(ctx, notificationsById, event, errors); } + return { notificationsById, errors }; } @@ -42,6 +45,8 @@ function handleCreate( notificationCreated: NotificationCreated.Event, errors: EventSourcingError[], ) { + logger.trace({ event: notificationCreated }, "Applying notification_created event"); + let notification = notificationsById.get(notificationCreated.notificationId); if (notification !== undefined) { errors.push( @@ -85,6 +90,7 @@ function applyRead( notificationRead: NotificationMarkedRead.Event, errors: EventSourcingError[], ) { + logger.trace({ event: notificationRead }, "Applying notification_mark_read event"); const notification = deepcopy(notificationsById.get(notificationRead.notificationId)); if (notification === undefined) { errors.push(new EventSourcingError({ ctx, event: notificationRead }, "notification not found")); diff --git a/api/src/service/domain/workflow/notification_list.ts b/api/src/service/domain/workflow/notification_list.ts index d1de8930b..4aaefcd33 100644 --- a/api/src/service/domain/workflow/notification_list.ts +++ b/api/src/service/domain/workflow/notification_list.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/notification_mark_read.ts b/api/src/service/domain/workflow/notification_mark_read.ts index 875f0f998..fddf5c55f 100644 --- a/api/src/service/domain/workflow/notification_mark_read.ts +++ b/api/src/service/domain/workflow/notification_mark_read.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -10,6 +10,7 @@ import * as UserRecord from "../organization/user_record"; import * as Notification from "./notification"; import { sourceNotifications } from "./notification_eventsourcing"; import * as NotificationMarkedRead from "./notification_marked_read"; +import logger from "lib/logger"; interface Repository { getUserNotificationEvents(userId: UserRecord.Id): Promise>; @@ -22,8 +23,10 @@ export async function markRead( repository: Repository, ): Promise> { const notificationEventsResult = await repository.getUserNotificationEvents(user.id); + if (Result.isErr(notificationEventsResult)) return new VError(notificationEventsResult, "could not get notification events"); + const notificationEvents = notificationEventsResult; const { notificationsById } = sourceNotifications(ctx, notificationEventsResult); @@ -32,7 +35,7 @@ export async function markRead( return new NotFound(ctx, "notification", notificationId); } - // Create the new event: + logger.trace({ user, notificationId }, "Creating notification_mark_read event"); const markedRead = NotificationMarkedRead.createEvent( ctx.source, user.id, @@ -50,7 +53,7 @@ export async function markRead( // No permission checked since every user should be able // to mark their own notifications as read - // Check that the new event is indeed valid: + logger.trace({ markedRead }, "Checking if markedRead event is valid"); const { errors } = sourceNotifications(ctx, notificationEvents.concat([markedRead])); if (errors.length > 0) { return new InvalidCommand(ctx, markedRead, errors); diff --git a/api/src/service/domain/workflow/notification_marked_read.ts b/api/src/service/domain/workflow/notification_marked_read.ts index 1e7d709c3..58eb82bd4 100644 --- a/api/src/service/domain/workflow/notification_marked_read.ts +++ b/api/src/service/domain/workflow/notification_marked_read.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as UserRecord from "../organization/user_record"; @@ -22,12 +22,8 @@ export interface Event { export const schema = Joi.object({ type: Joi.valid(eventType).required(), - source: Joi.string() - .allow("") - .required(), - time: Joi.date() - .iso() - .required(), + source: Joi.string().allow("").required(), + time: Joi.date().iso().required(), publisher: Joi.string().required(), notificationId: Notification.idSchema.required(), recipient: UserRecord.idSchema, @@ -48,6 +44,7 @@ export function createEvent( notificationId, recipient, }; + logger.trace("Creating notification_mark_read event"); const validationResult = validate(event); if (Result.isErr(validationResult)) { diff --git a/api/src/service/domain/workflow/project_assign.spec.ts b/api/src/service/domain/workflow/project_assign.spec.ts index 861fcef2f..29c9a01a2 100644 --- a/api/src/service/domain/workflow/project_assign.spec.ts +++ b/api/src/service/domain/workflow/project_assign.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_assign.ts b/api/src/service/domain/workflow/project_assign.ts index 192eda6b7..c3588222f 100644 --- a/api/src/service/domain/workflow/project_assign.ts +++ b/api/src/service/domain/workflow/project_assign.ts @@ -1,5 +1,6 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -35,19 +36,19 @@ export async function assignProject( return { newEvents: [], project }; } - // Create the new event: + logger.trace({ issuer, assignee, projectId }, "Creating new project_assign event"); const projectAssigned = ProjectAssigned.createEvent(ctx.source, issuer.id, projectId, assignee); if (Result.isErr(projectAssigned)) { return new VError(projectAssigned, "failed to create event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking authorization of issuer"); const intent = "project.assign"; if (issuer.id !== "root" && !Project.permits(project, issuer, [intent])) { return new NotAuthorized({ ctx, userId: issuer.id, intent, target: project }); } - // Check that the new event is indeed valid: + logger.trace({ event: projectAssigned }, "Checking if project_assigned event is valid"); const result = ProjectEventSourcing.newProjectFromEvent(ctx, project, projectAssigned); if (Result.isErr(result)) { return new InvalidCommand(ctx, projectAssigned, [result]); @@ -61,21 +62,25 @@ export async function assignProject( } const notifications = recipientsResult.reduce((notifications, recipient) => { // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { - const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - projectAssigned, - projectId, - ); - if (Result.isErr(notification)) { - return new VError(notification, "failed to create notification event"); - } - notifications.push(notification); + if (recipient === issuer.id) { + return notifications; } + + const notification = NotificationCreated.createEvent( + ctx.source, + issuer.id, + recipient, + projectAssigned, + projectId, + ); + if (Result.isErr(notification)) { + return new VError(notification, "failed to create notification event"); + } + notifications.push(notification); + return notifications; }, [] as NotificationCreated.Event[]); + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification events"); } diff --git a/api/src/service/domain/workflow/project_assigned.ts b/api/src/service/domain/workflow/project_assigned.ts index 73bf044fd..fb8aa4d75 100644 --- a/api/src/service/domain/workflow/project_assigned.ts +++ b/api/src/service/domain/workflow/project_assigned.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Project from "./project"; diff --git a/api/src/service/domain/workflow/project_close.spec.ts b/api/src/service/domain/workflow/project_close.spec.ts index 1006ab707..6a58a3630 100644 --- a/api/src/service/domain/workflow/project_close.spec.ts +++ b/api/src/service/domain/workflow/project_close.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/project_close.ts b/api/src/service/domain/workflow/project_close.ts index f44b5392d..cee2ee799 100644 --- a/api/src/service/domain/workflow/project_close.ts +++ b/api/src/service/domain/workflow/project_close.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as Project from "./project"; import * as ProjectClosed from "./project_closed"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as Subproject from "./subproject"; +import logger from "lib/logger"; interface Repository { getProject(): Promise>; @@ -37,12 +38,13 @@ export async function closeProject( return { newEvents: [], project }; } - // Create the new event: + logger.trace({ issuer, projectId }, "Creating new project_closed event"); const projectClosed = ProjectClosed.createEvent(ctx.source, issuer.id, projectId); if (Result.isErr(projectClosed)) { return new VError(projectClosed, "failed to create event"); } + logger.trace({ issuer }, "Checking authorization of issuer"); const assignedIdentitiesResult = await repository.getUsersForIdentity(project.assignee); if (Result.isErr(assignedIdentitiesResult)) { return new VError(assignedIdentitiesResult, `fetch users for ${project.assignee} failed`); @@ -57,6 +59,7 @@ export async function closeProject( ); } + logger.trace({ projectId }, "Checking if all subprojects of this project are already closed"); // Make sure all subprojects are already closed (we rely on closeSubproject doing a // similar check with respect to the subproject's workflowitems): const subprojects = await repository.getSubprojects(projectId); @@ -67,11 +70,12 @@ export async function closeProject( `could not find subprojects for project ${projectId}`, ); } + if (subprojects.some((x) => x.status !== "closed")) { return new PreconditionError(ctx, projectClosed, "at least one subproject is not closed yet"); } - // Check that the new event is indeed valid: + logger.trace({ event: projectClosed }, "Checking validity of project_closed event"); const validationResult = ProjectEventSourcing.newProjectFromEvent(ctx, project, projectClosed); if (Result.isErr(validationResult)) { return new InvalidCommand(ctx, projectClosed, [validationResult]); diff --git a/api/src/service/domain/workflow/project_closed.ts b/api/src/service/domain/workflow/project_closed.ts index 2fbd410cf..415fcd85d 100644 --- a/api/src/service/domain/workflow/project_closed.ts +++ b/api/src/service/domain/workflow/project_closed.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Project from "./project"; diff --git a/api/src/service/domain/workflow/project_create.spec.ts b/api/src/service/domain/workflow/project_create.spec.ts index b29f8733c..ef3a2f030 100644 --- a/api/src/service/domain/workflow/project_create.spec.ts +++ b/api/src/service/domain/workflow/project_create.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { AlreadyExists } from "../errors/already_exists"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/project_create.ts b/api/src/service/domain/workflow/project_create.ts index 0d5abdd74..f57be2be3 100644 --- a/api/src/service/domain/workflow/project_create.ts +++ b/api/src/service/domain/workflow/project_create.ts @@ -1,8 +1,9 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { projectIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { randomString } from "../../hash"; import * as AdditionalData from "../additional_data"; @@ -66,6 +67,7 @@ export async function createProject( const source = ctx.source; const publisher = creatingUser.id; + logger.trace({ req: data }, "Trying to create 'ProjectCreated' Event from request data"); const createEvent = ProjectCreated.createEvent(source, publisher, { id: data.id || randomString(), status: data.status || "open", @@ -82,7 +84,7 @@ export async function createProject( return new VError(createEvent, "failed to create project created event"); } - // Make sure for each organization and currency there is only one entry: + logger.trace({ event: createEvent }, "Checking if entry is unique per organization and currency"); const badEntry = findDuplicateBudgetEntry(createEvent.project.projectedBudgets); if (badEntry !== undefined) { return new AlreadyExists( @@ -103,7 +105,7 @@ export async function createProject( return new PreconditionError(ctx, createEvent, "user 'root' is not allowed to create projects"); } - // Check authorization + logger.trace({ user: creatingUser }, "Check for user is permitted globally"); const intent = "global.createProject"; const globalPermissionsResult = await repository.getGlobalPermissions(); if (Result.isErr(globalPermissionsResult)) { @@ -114,7 +116,7 @@ export async function createProject( return new NotAuthorized({ ctx, userId: creatingUser.id, intent, target: globalPermissions }); } - // Check that the event is valid + logger.trace({ event: createEvent }, "Checking if Event is valid"); const result = ProjectCreated.createFrom(ctx, createEvent); if (Result.isErr(result)) { return new InvalidCommand(ctx, createEvent, [result]); diff --git a/api/src/service/domain/workflow/project_created.ts b/api/src/service/domain/workflow/project_created.ts index de1fb1a4e..6109ce4d5 100644 --- a/api/src/service/domain/workflow/project_created.ts +++ b/api/src/service/domain/workflow/project_created.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; - -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -62,6 +62,8 @@ export function createEvent( project: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating project_created event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/project_eventsourcing.ts b/api/src/service/domain/workflow/project_eventsourcing.ts index 1b4d64179..b6ed27007 100644 --- a/api/src/service/domain/workflow/project_eventsourcing.ts +++ b/api/src/service/domain/workflow/project_eventsourcing.ts @@ -1,7 +1,7 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -15,6 +15,7 @@ import * as ProjectProjectedBudgetDeleted from "./project_projected_budget_delet import * as ProjectProjectedBudgetUpdated from "./project_projected_budget_updated"; import { ProjectTraceEvent } from "./project_trace_event"; import * as ProjectUpdated from "./project_updated"; +import logger from "lib/logger"; export function sourceProjects( ctx: Ctx, @@ -28,6 +29,7 @@ export function sourceProjects( const errors: Error[] = []; for (const event of events) { + logger.trace({ event }, "Validating project Event by applying it"); if (!event.type.startsWith("project_")) { continue; } diff --git a/api/src/service/domain/workflow/project_get.spec.ts b/api/src/service/domain/workflow/project_get.spec.ts index ffaaa6bfc..acf4924fd 100644 --- a/api/src/service/domain/workflow/project_get.spec.ts +++ b/api/src/service/domain/workflow/project_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/project_get.ts b/api/src/service/domain/workflow/project_get.ts index e1d82c63c..cec40e2d9 100644 --- a/api/src/service/domain/workflow/project_get.ts +++ b/api/src/service/domain/workflow/project_get.ts @@ -1,5 +1,5 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -48,22 +48,31 @@ function dropHiddenHistoryEvents( project: Project.Project, actingUser: ServiceUser, ): Project.Project { - const isEventVisible = - actingUser.id === "root" - ? () => true - : (event: ProjectTraceEvent) => { - const allowed = requiredPermissions.get(event.businessEvent.type); - if (!allowed) return false; - for (const intent of allowed) { - for (const identity of project.permissions[intent] || []) { - if (canAssumeIdentity(actingUser, identity)) return true; - } - } - return false; - }; + const isEventVisible = getIsEventVisibleFunction(actingUser, project); return { ...project, log: (project.log || []).filter(isEventVisible), }; } + +function getIsEventVisibleFunction( + actingUser: ServiceUser, + project: Project.Project, +): (event?: ProjectTraceEvent) => boolean { + if (actingUser.id === "root") { + return () => true; + } + + return (event: ProjectTraceEvent) => { + const allowed = requiredPermissions.get(event.businessEvent.type); + if (!allowed) return false; + + for (const intent of allowed) { + for (const identity of project.permissions[intent] || []) { + if (canAssumeIdentity(actingUser, identity)) return true; + } + } + return false; + }; +} diff --git a/api/src/service/domain/workflow/project_history_get.spec.ts b/api/src/service/domain/workflow/project_history_get.spec.ts index 4498aaa7c..65097c5e8 100644 --- a/api/src/service/domain/workflow/project_history_get.spec.ts +++ b/api/src/service/domain/workflow/project_history_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_history_get.ts b/api/src/service/domain/workflow/project_history_get.ts index 2030f99fd..1c4b63a9e 100644 --- a/api/src/service/domain/workflow/project_history_get.ts +++ b/api/src/service/domain/workflow/project_history_get.ts @@ -1,5 +1,5 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -7,6 +7,7 @@ import { ServiceUser } from "../organization/service_user"; import { Filter, filterTraceEvents } from "./historyFilter"; import * as Project from "./project"; import { ProjectTraceEvent } from "./project_trace_event"; +import logger from "lib/logger"; interface Repository { getProject(projectId): Promise>; @@ -25,10 +26,14 @@ export const getHistory = async ( return new NotFound(ctx, "project", projectId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intents: Intent[] = ["project.viewDetails", "project.viewHistory"]; - if (!(Project.permits(project, user, [intents[0]]) || - Project.permits(project, user, [intents[1]]))) { + if ( + !( + Project.permits(project, user, [intents[0]]) || Project.permits(project, user, [intents[1]]) + ) + ) { return new NotAuthorized({ ctx, userId: user.id, intent: intents, target: project }); } } diff --git a/api/src/service/domain/workflow/project_list.spec.ts b/api/src/service/domain/workflow/project_list.spec.ts index 4430732d6..8bf4a5f4e 100644 --- a/api/src/service/domain/workflow/project_list.spec.ts +++ b/api/src/service/domain/workflow/project_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { Project } from "./project"; diff --git a/api/src/service/domain/workflow/project_list.ts b/api/src/service/domain/workflow/project_list.ts index b3159512d..fa03788f1 100644 --- a/api/src/service/domain/workflow/project_list.ts +++ b/api/src/service/domain/workflow/project_list.ts @@ -1,13 +1,11 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; -import { BusinessEvent } from "../business_event"; +import { Ctx } from "lib/ctx"; import { canAssumeIdentity } from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; import * as Project from "./project"; -import { sourceProjects } from "./project_eventsourcing"; import { ProjectTraceEvent } from "./project_trace_event"; -import { VError } from "verror"; import * as Result from "../../../result"; +import logger from "lib/logger"; interface Repository { getAllProjects(): Promise; @@ -20,6 +18,7 @@ export async function getAllVisible( ): Promise> { const allProjects = await repository.getAllProjects(); + logger.trace({ user }, "Filtering projects visible to user"); const isVisible = user.id === "root" ? () => true @@ -49,22 +48,29 @@ function dropHiddenHistoryEvents( project: Project.Project, actingUser: ServiceUser, ): Project.Project { - const isEventVisible = - actingUser.id === "root" - ? () => true - : (event: ProjectTraceEvent) => { - const allowed = requiredPermissions.get(event.businessEvent.type); - if (!allowed) return false; - for (const intent of allowed) { - for (const identity of project.permissions[intent] || []) { - if (canAssumeIdentity(actingUser, identity)) return true; - } - } - return false; - }; + const isEventVisible = getIsEventVisibleFunction(actingUser, project); return { ...project, log: (project.log || []).filter(isEventVisible), }; } + +function getIsEventVisibleFunction( + actingUser: ServiceUser, + project: Project.Project, +): (event?: ProjectTraceEvent) => boolean { + if (actingUser.id === "root") return () => true; + + return (event: ProjectTraceEvent) => { + const allowed = requiredPermissions.get(event.businessEvent.type); + if (!allowed) return false; + + for (const intent of allowed) { + for (const identity of project.permissions[intent] || []) { + if (canAssumeIdentity(actingUser, identity)) return true; + } + } + return false; + }; +} diff --git a/api/src/service/domain/workflow/project_permission_grant.spec.ts b/api/src/service/domain/workflow/project_permission_grant.spec.ts index bac7b66b4..08eb0473b 100644 --- a/api/src/service/domain/workflow/project_permission_grant.spec.ts +++ b/api/src/service/domain/workflow/project_permission_grant.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_permission_grant.ts b/api/src/service/domain/workflow/project_permission_grant.ts index 9efed9def..e2e6292aa 100644 --- a/api/src/service/domain/workflow/project_permission_grant.ts +++ b/api/src/service/domain/workflow/project_permission_grant.ts @@ -1,7 +1,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -12,6 +12,7 @@ import { ServiceUser } from "../organization/service_user"; import * as Project from "./project"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as ProjectPermissionGranted from "./project_permission_granted"; +import logger from "lib/logger"; interface Repository { getProject(projectId: Project.Id): Promise>; @@ -43,7 +44,7 @@ export async function grantProjectPermission( return new VError(permissionGranted, "failed to create permission granted event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const grantIntent = "project.intent.grantPermission"; if (!Project.permits(project, issuer, [grantIntent])) { diff --git a/api/src/service/domain/workflow/project_permission_granted.ts b/api/src/service/domain/workflow/project_permission_granted.ts index 17666fd3c..38e55c89d 100644 --- a/api/src/service/domain/workflow/project_permission_granted.ts +++ b/api/src/service/domain/workflow/project_permission_granted.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import Intent, { projectIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -37,6 +37,8 @@ export function createEvent( grantee: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating project_premission_granted event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/project_permission_revoke.spec.ts b/api/src/service/domain/workflow/project_permission_revoke.spec.ts index be55bc3b5..0ca8f0b6c 100644 --- a/api/src/service/domain/workflow/project_permission_revoke.spec.ts +++ b/api/src/service/domain/workflow/project_permission_revoke.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_permission_revoke.ts b/api/src/service/domain/workflow/project_permission_revoke.ts index 2f534e88f..13c3cc239 100644 --- a/api/src/service/domain/workflow/project_permission_revoke.ts +++ b/api/src/service/domain/workflow/project_permission_revoke.ts @@ -1,7 +1,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import { ServiceUser } from "../organization/service_user"; import * as Project from "./project"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as ProjectPermissionRevoked from "./project_permission_revoked"; +import logger from "lib/logger"; interface Repository { getProject(projectId: Project.Id): Promise>; @@ -44,7 +45,7 @@ export async function revokeProjectPermission( return new VError(permissionRevoked, "failed to create permission revoked event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const revokeIntent = "project.intent.revokePermission"; if (!Project.permits(project, issuer, [revokeIntent])) { @@ -52,19 +53,15 @@ export async function revokeProjectPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionRevoked }, "Checking event validity"); const updatedProject = ProjectEventSourcing.newProjectFromEvent(ctx, project, permissionRevoked); if (Result.isErr(updatedProject)) { return new InvalidCommand(ctx, permissionRevoked, [updatedProject]); } - // Prevent revoking grant permission of last user + logger.trace("Prevent revoking grant permission of last user"); const intents: Intent[] = ["project.intent.grantPermission"]; - if ( - intent && - intents.includes(intent) && - project?.permissions[intent]?.length === 1 - ) { + if (intent && intents.includes(intent) && project?.permissions[intent]?.length === 1) { return new PreconditionError( ctx, permissionRevoked, @@ -75,7 +72,7 @@ export async function revokeProjectPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(project.permissions, updatedProject.permissions)) { return []; - } else { - return [permissionRevoked]; } + + return [permissionRevoked]; } diff --git a/api/src/service/domain/workflow/project_permission_revoked.ts b/api/src/service/domain/workflow/project_permission_revoked.ts index d89b3cfd7..0a7a97bca 100644 --- a/api/src/service/domain/workflow/project_permission_revoked.ts +++ b/api/src/service/domain/workflow/project_permission_revoked.ts @@ -1,6 +1,6 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import Intent, { projectIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; @@ -37,6 +37,8 @@ export function createEvent( revokee: Identity, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating project_premission_revoked event"); + const event = { type: eventType, source, @@ -69,6 +71,7 @@ export function validate(input: any): Result.Type { * `project_eventsourcing.ts`:`newProjectFromEvent`. */ export function mutate(project: Project.Project, event: Event): Result.Type { + logger.trace({ event, project }, "Applying event to the given project"); if (event.type !== "project_permission_revoked") { return new VError(`illegal event type: ${event.type}`); } diff --git a/api/src/service/domain/workflow/project_permissions_list.spec.ts b/api/src/service/domain/workflow/project_permissions_list.spec.ts index fbffd7dc1..6b6a21b6a 100644 --- a/api/src/service/domain/workflow/project_permissions_list.spec.ts +++ b/api/src/service/domain/workflow/project_permissions_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/project_permissions_list.ts b/api/src/service/domain/workflow/project_permissions_list.ts index 448003e56..44f696521 100644 --- a/api/src/service/domain/workflow/project_permissions_list.ts +++ b/api/src/service/domain/workflow/project_permissions_list.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -24,11 +25,13 @@ export async function getProjectPermissions( const project: Project.Project = projectResult; + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intent = "project.intent.listPermissions"; if (!Project.permits(project, user, [intent])) { return new NotAuthorized({ ctx, userId: user.id, intent, target: project }); } } + return project.permissions; } diff --git a/api/src/service/domain/workflow/project_projected_budget_delete.spec.ts b/api/src/service/domain/workflow/project_projected_budget_delete.spec.ts index 8ec0d26d5..0342a0471 100644 --- a/api/src/service/domain/workflow/project_projected_budget_delete.spec.ts +++ b/api/src/service/domain/workflow/project_projected_budget_delete.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_projected_budget_delete.ts b/api/src/service/domain/workflow/project_projected_budget_delete.ts index 1b7060e5e..1652eea8c 100644 --- a/api/src/service/domain/workflow/project_projected_budget_delete.ts +++ b/api/src/service/domain/workflow/project_projected_budget_delete.ts @@ -1,6 +1,6 @@ import { isEqual } from "lodash"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as Project from "./project"; import { ProjectedBudget } from "./projected_budget"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as ProjectProjectedBudgetDeleted from "./project_projected_budget_deleted"; +import logger from "lib/logger"; interface Repository { getProject(projectId: Project.Id): Promise>; @@ -36,7 +37,10 @@ export async function deleteProjectedBudget( return new NotFound(ctx, "project", projectId); } - // Create the new event: + logger.trace( + { issuer, organization, projectId }, + "Creating project_projected_budget_deleted event", + ); const budgetDeleted = ProjectProjectedBudgetDeleted.createEvent( ctx.source, issuer.id, @@ -48,7 +52,7 @@ export async function deleteProjectedBudget( return new VError(budgetDeleted, "failed to create projected budget deleted event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "project.budget.deleteProjected"; if (!Project.permits(project, issuer, [intent])) { @@ -56,7 +60,7 @@ export async function deleteProjectedBudget( } } - // Check that the new event is indeed valid: + logger.trace({ event: budgetDeleted }, "Checking event validity"); const result = ProjectEventSourcing.newProjectFromEvent(ctx, project, budgetDeleted); if (Result.isErr(result)) { return new InvalidCommand(ctx, budgetDeleted, [result]); @@ -65,17 +69,18 @@ export async function deleteProjectedBudget( // Only emit the event if it causes any changes: if (isEqual(project.projectedBudgets, result.projectedBudgets)) { return { newEvents: [], projectedBudgets: result.projectedBudgets }; - } else { - // Create notification events: - const recipientsResult = project.assignee - ? await repository.getUsersForIdentity(project.assignee) - : []; - if (Result.isErr(recipientsResult)) { - return new VError(recipientsResult, `fetch users for ${project.assignee} failed`); - } - const notifications = recipientsResult.reduce((notifications, recipient) => { - // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { + } + + const recipientsResult = project.assignee + ? await repository.getUsersForIdentity(project.assignee) + : []; + if (Result.isErr(recipientsResult)) { + return new VError(recipientsResult, `fetch users for ${project.assignee} failed`); + } + + const notifications = recipientsResult.reduce((notifications, recipient) => { + // The issuer doesn't receive a notification: + if (recipient !== issuer.id) { const notification = NotificationCreated.createEvent( ctx.source, issuer.id, @@ -87,15 +92,16 @@ export async function deleteProjectedBudget( return new VError(notification, "failed to create event"); } notifications.push(notification); - } - return notifications; - }, [] as NotificationCreated.Event[]); - if (Result.isErr(notifications)) { - return new VError(notifications, "failed to create notification events"); } - return { - newEvents: [budgetDeleted, ...notifications], - projectedBudgets: result.projectedBudgets, - }; + return notifications; + }, [] as NotificationCreated.Event[]); + + if (Result.isErr(notifications)) { + return new VError(notifications, "failed to create notification events"); } + + return { + newEvents: [budgetDeleted, ...notifications], + projectedBudgets: result.projectedBudgets, + }; } diff --git a/api/src/service/domain/workflow/project_projected_budget_deleted.ts b/api/src/service/domain/workflow/project_projected_budget_deleted.ts index 143b3af5d..dfcf58cff 100644 --- a/api/src/service/domain/workflow/project_projected_budget_deleted.ts +++ b/api/src/service/domain/workflow/project_projected_budget_deleted.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import { CurrencyCode, currencyCodeSchema } from "./money"; diff --git a/api/src/service/domain/workflow/project_projected_budget_update.spec.ts b/api/src/service/domain/workflow/project_projected_budget_update.spec.ts index a5d053197..5841d8fe7 100644 --- a/api/src/service/domain/workflow/project_projected_budget_update.spec.ts +++ b/api/src/service/domain/workflow/project_projected_budget_update.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_projected_budget_update.ts b/api/src/service/domain/workflow/project_projected_budget_update.ts index 54c50e053..50669ec51 100644 --- a/api/src/service/domain/workflow/project_projected_budget_update.ts +++ b/api/src/service/domain/workflow/project_projected_budget_update.ts @@ -1,6 +1,6 @@ import { isEqual } from "lodash"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as Project from "./project"; import { ProjectedBudget } from "./projected_budget"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as ProjectProjectedBudgetUpdated from "./project_projected_budget_updated"; +import logger from "lib/logger"; interface Repository { getProject(projectId: Project.Id): Promise>; @@ -37,7 +38,10 @@ export async function updateProjectedBudget( return new NotFound(ctx, "project", projectId); } - // Create the new event: + logger.trace( + { issuer, organization, projectId }, + "Creating project_projected_budget_updated event", + ); const budgetUpdated = ProjectProjectedBudgetUpdated.createEvent( ctx.source, issuer.id, @@ -50,7 +54,7 @@ export async function updateProjectedBudget( return new VError(budgetUpdated, "failed to create projected budget updated event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "project.budget.updateProjected"; if (!Project.permits(project, issuer, [intent])) { @@ -58,7 +62,7 @@ export async function updateProjectedBudget( } } - // Check that the new event is indeed valid: + logger.trace({ event: budgetUpdated }, "Checking event validity"); const result = ProjectEventSourcing.newProjectFromEvent(ctx, project, budgetUpdated); if (Result.isErr(result)) { return new InvalidCommand(ctx, budgetUpdated, [result]); @@ -67,37 +71,40 @@ export async function updateProjectedBudget( // Only emit the event if it causes any changes: if (isEqual(project.projectedBudgets, result.projectedBudgets)) { return { newEvents: [], projectedBudgets: result.projectedBudgets }; - } else { - // Create notification events: - const recipientsResult = project.assignee - ? await repository.getUsersForIdentity(project.assignee) - : []; - if (Result.isErr(recipientsResult)) { - return new VError(recipientsResult, `fetch users for ${project.assignee} failed`); - } - const notifications = recipientsResult.reduce((notifications, recipient) => { - // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { - const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - budgetUpdated, - projectId, - ); - if (Result.isErr(notification)) { - return new VError(notification, "failed to create event"); - } - notifications.push(notification); + } + + logger.trace("Creating notification events"); + const recipientsResult = project.assignee + ? await repository.getUsersForIdentity(project.assignee) + : []; + if (Result.isErr(recipientsResult)) { + return new VError(recipientsResult, `fetch users for ${project.assignee} failed`); + } + + const notifications = recipientsResult.reduce((notifications, recipient) => { + // The issuer doesn't receive a notification: + if (recipient !== issuer.id) { + const notification = NotificationCreated.createEvent( + ctx.source, + issuer.id, + recipient, + budgetUpdated, + projectId, + ); + if (Result.isErr(notification)) { + return new VError(notification, "failed to create event"); } - return notifications; - }, [] as NotificationCreated.Event[]); - if (Result.isErr(notifications)) { - return new VError(notifications, "failed to create notification events"); + notifications.push(notification); } - return { - newEvents: [budgetUpdated, ...notifications], - projectedBudgets: result.projectedBudgets, - }; + return notifications; + }, [] as NotificationCreated.Event[]); + + if (Result.isErr(notifications)) { + return new VError(notifications, "failed to create notification events"); } + + return { + newEvents: [budgetUpdated, ...notifications], + projectedBudgets: result.projectedBudgets, + }; } diff --git a/api/src/service/domain/workflow/project_projected_budget_updated.ts b/api/src/service/domain/workflow/project_projected_budget_updated.ts index f429986fc..9882b9d7b 100644 --- a/api/src/service/domain/workflow/project_projected_budget_updated.ts +++ b/api/src/service/domain/workflow/project_projected_budget_updated.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import { CurrencyCode, currencyCodeSchema, MoneyAmount, moneyAmountSchema } from "./money"; diff --git a/api/src/service/domain/workflow/project_update.spec.ts b/api/src/service/domain/workflow/project_update.spec.ts index cf6dbcca7..527d49040 100644 --- a/api/src/service/domain/workflow/project_update.spec.ts +++ b/api/src/service/domain/workflow/project_update.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/project_update.ts b/api/src/service/domain/workflow/project_update.ts index 8b01a796d..8312223df 100644 --- a/api/src/service/domain/workflow/project_update.ts +++ b/api/src/service/domain/workflow/project_update.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); import { isEqual } from "lodash"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as NotificationCreated from "./notification_created"; import * as Project from "./project"; import * as ProjectEventSourcing from "./project_eventsourcing"; import * as ProjectUpdated from "./project_updated"; +import logger from "lib/logger"; export type RequestData = ProjectUpdated.Modification; export const requestDataSchema = ProjectUpdated.modificationSchema; @@ -41,14 +42,14 @@ export async function updateProject( return new NotFound(ctx, "project", projectId); } - // Create the new event: + logger.trace({ issuer, data, projectId }, "Creating project_updated event"); const projectUpdatedResult = ProjectUpdated.createEvent(ctx.source, issuer.id, projectId, data); if (Result.isErr(projectUpdatedResult)) { return new VError(projectUpdatedResult, "create update-event failed"); } const projectUpdated = projectUpdatedResult; - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "project.update"; if (!Project.permits(project, issuer, [intent])) { @@ -56,7 +57,7 @@ export async function updateProject( } } - // Check that the new event is indeed valid: + logger.trace({ event: projectUpdated }, "Checking event validity"); const result = ProjectEventSourcing.newProjectFromEvent(ctx, project, projectUpdated); if (Result.isErr(result)) { return new InvalidCommand(ctx, projectUpdated, [result]); @@ -67,13 +68,15 @@ export async function updateProject( return []; } - // Create notification events: + logger.trace("Creating notification events"); let notifications: Result.Type = []; if (project.assignee !== undefined) { const recipientsResult = await repository.getUsersForIdentity(project.assignee); + if (Result.isErr(recipientsResult)) { return new VError(recipientsResult, `fetch users for ${project.assignee} failed`); } + notifications = recipientsResult.reduce((notifications, recipient) => { // The issuer doesn't receive a notification: if (recipient !== issuer.id) { @@ -91,6 +94,7 @@ export async function updateProject( } return notifications; }, [] as NotificationCreated.Event[]); + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification created events"); } diff --git a/api/src/service/domain/workflow/project_updated.ts b/api/src/service/domain/workflow/project_updated.ts index f3e1b9199..1232078e6 100644 --- a/api/src/service/domain/workflow/project_updated.ts +++ b/api/src/service/domain/workflow/project_updated.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { Identity } from "../organization/identity"; @@ -55,7 +54,7 @@ export function createEvent( projectId: Project.Id, modification: Modification, time: string = new Date().toISOString(), -): Result.Type { +): Result.Type { const event = { type: eventType, source, @@ -64,7 +63,9 @@ export function createEvent( projectId, update: modification, }; + const validationResult = validate(event); + if (Result.isErr(validationResult)) { return new VError(validationResult, `not a valid ${eventType} event`); } diff --git a/api/src/service/domain/workflow/subproject_assign.spec.ts b/api/src/service/domain/workflow/subproject_assign.spec.ts index 19ef44938..45cb8d9c6 100644 --- a/api/src/service/domain/workflow/subproject_assign.spec.ts +++ b/api/src/service/domain/workflow/subproject_assign.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_assign.ts b/api/src/service/domain/workflow/subproject_assign.ts index dcbc475aa..5246bbfbc 100644 --- a/api/src/service/domain/workflow/subproject_assign.ts +++ b/api/src/service/domain/workflow/subproject_assign.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import * as Project from "./project"; import * as Subproject from "./subproject"; import * as SubprojectAssigned from "./subproject_assigned"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; +import logger from "lib/logger"; interface Repository { getSubproject(): Promise>; @@ -37,7 +38,7 @@ export async function assignSubproject( return { newEvents: [], subproject }; } - // Create the new event: + logger.trace({ issuer, assignee, projectId, subprojectId }, "Creating suproject_assign event"); const subprojectAssignedResult = SubprojectAssigned.createEvent( ctx.source, issuer.id, @@ -50,7 +51,7 @@ export async function assignSubproject( } const subprojectAssigned = subprojectAssignedResult; - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "subproject.assign"; if (!Subproject.permits(subproject, issuer, [intent])) { @@ -58,7 +59,7 @@ export async function assignSubproject( } } - // Check that the new event is indeed valid: + logger.trace({ event: subprojectAssigned }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent( ctx, subproject, @@ -69,7 +70,7 @@ export async function assignSubproject( } subproject = result; - // Create notification events: + logger.trace("Creating notifications"); const recipientsResult = await repository.getUsersForIdentity(assignee); if (Result.isErr(recipientsResult)) { return new VError(recipientsResult, `fetch users for ${assignee} failed`); @@ -78,21 +79,23 @@ export async function assignSubproject( // The issuer doesn't receive a notification: if (recipient !== issuer.id) { const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - subprojectAssigned, - projectId, - ); + ctx.source, + issuer.id, + recipient, + subprojectAssigned, + projectId, + ); if (Result.isErr(notification)) { - return new VError(notification, "failed to create notification event"); - } + return new VError(notification, "failed to create notification event"); + } notifications.push(notification); } return notifications; }, [] as NotificationCreated.Event[]); + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification events"); } + return { newEvents: [subprojectAssigned, ...notifications], subproject }; } diff --git a/api/src/service/domain/workflow/subproject_assigned.ts b/api/src/service/domain/workflow/subproject_assigned.ts index 364be9764..185246aba 100644 --- a/api/src/service/domain/workflow/subproject_assigned.ts +++ b/api/src/service/domain/workflow/subproject_assigned.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Project from "./project"; diff --git a/api/src/service/domain/workflow/subproject_close.spec.ts b/api/src/service/domain/workflow/subproject_close.spec.ts index 2c9ee5efa..079bc9b5c 100644 --- a/api/src/service/domain/workflow/subproject_close.spec.ts +++ b/api/src/service/domain/workflow/subproject_close.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/subproject_close.ts b/api/src/service/domain/workflow/subproject_close.ts index 40bbaf3d4..23e555166 100644 --- a/api/src/service/domain/workflow/subproject_close.ts +++ b/api/src/service/domain/workflow/subproject_close.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import * as Subproject from "./subproject"; import * as SubprojectClosed from "./subproject_closed"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as Workflowitem from "./workflowitem"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -45,7 +46,7 @@ export async function closeSubproject( return { newEvents: [], subproject }; } - // Create the new event: + logger.trace({ issuer, projectId, subprojectId }, "Creating subproject_closed event"); const subprojectClosed = SubprojectClosed.createEvent( ctx.source, issuer.id, @@ -70,7 +71,10 @@ export async function closeSubproject( ); } - // Make sure all workflowitems are already closed: + logger.trace( + { projectId, subprojectId }, + "Making sure all workflowitems of subproject are already closed", + ); const workflowitems = await repository.getWorkflowitems(projectId, subprojectId); if (Result.isErr(workflowitems)) { return new PreconditionError( @@ -87,14 +91,14 @@ export async function closeSubproject( ); } - // Check that the new event is indeed valid: + logger.trace({ event: subprojectClosed }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent(ctx, subproject, subprojectClosed); if (Result.isErr(result)) { return new InvalidCommand(ctx, subprojectClosed, [result]); } subproject = result; - // Create notification events: + logger.trace("Creating notification events"); const notifications: Result.Type = assignedIdentities.reduce( (notifications, recipient) => { // The issuer doesn't receive a notification: @@ -115,8 +119,10 @@ export async function closeSubproject( }, [] as NotificationCreated.Event[], ); + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification events"); } + return { newEvents: [subprojectClosed, ...notifications], subproject }; } diff --git a/api/src/service/domain/workflow/subproject_closed.ts b/api/src/service/domain/workflow/subproject_closed.ts index dbbfdf78c..b5f39f9f9 100644 --- a/api/src/service/domain/workflow/subproject_closed.ts +++ b/api/src/service/domain/workflow/subproject_closed.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import * as Result from "../../../result"; import { Identity } from "../organization/identity"; import * as Project from "./project"; diff --git a/api/src/service/domain/workflow/subproject_create.spec.ts b/api/src/service/domain/workflow/subproject_create.spec.ts index 59619201f..1cf864430 100644 --- a/api/src/service/domain/workflow/subproject_create.spec.ts +++ b/api/src/service/domain/workflow/subproject_create.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { AlreadyExists } from "../errors/already_exists"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/subproject_create.ts b/api/src/service/domain/workflow/subproject_create.ts index 70199e99b..d12e8069c 100644 --- a/api/src/service/domain/workflow/subproject_create.ts +++ b/api/src/service/domain/workflow/subproject_create.ts @@ -1,8 +1,8 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; - import Intent, { subprojectIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { randomString } from "../../hash"; import * as AdditionalData from "../additional_data"; @@ -14,13 +14,14 @@ import { PreconditionError } from "../errors/precondition_error"; import * as AuthToken from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; import { Permissions } from "../permissions"; +import WorkflowitemType, { workflowitemTypeSchema } from "../workflowitem_types/types"; import { CurrencyCode, currencyCodeSchema } from "./money"; import * as Project from "./project"; import { ProjectedBudget, projectedBudgetListSchema } from "./projected_budget"; -import WorkflowitemType, { workflowitemTypeSchema } from "../workflowitem_types/types"; import * as Subproject from "./subproject"; import * as SubprojectCreated from "./subproject_created"; + export interface RequestData { projectId: Project.Id; subprojectId?: Subproject.Id; @@ -69,6 +70,8 @@ export async function createSubproject( const projectId = reqData.projectId; const subprojectId = reqData.subprojectId || randomString(); + + logger.trace({ req: reqData }, "Trying to create 'SubprojectCreated' Event from request data"); const createEvent = SubprojectCreated.createEvent(ctx.source, publisher, projectId, { id: subprojectId, status: reqData.status || "open", @@ -87,6 +90,7 @@ export async function createSubproject( } // Make sure for each organization and currency there is only one entry: + logger.trace({ event: createEvent }, "Checking if entry is unique per organization and currency"); const badEntry = findDuplicateBudgetEntry(createEvent.subproject.projectedBudgets); if (badEntry !== undefined) { return new AlreadyExists( @@ -113,6 +117,7 @@ export async function createSubproject( } // Check authorization (if not root): + logger.trace({ user: creatingUser }, "Checking authorization of user"); const projectPermissions = await repository.projectPermissions(projectId); if (Result.isErr(projectPermissions)) { return new NotFound(ctx, "project", projectId); @@ -136,7 +141,7 @@ export async function createSubproject( ); } - // Check that the event is valid: + logger.trace({ event: createEvent }, "Checking if Event is valid"); const result = SubprojectCreated.createFrom(ctx, createEvent); if (Result.isErr(result)) { return new InvalidCommand(ctx, createEvent, [result]); diff --git a/api/src/service/domain/workflow/subproject_created.ts b/api/src/service/domain/workflow/subproject_created.ts index 9cfb6809a..4e7780d1e 100644 --- a/api/src/service/domain/workflow/subproject_created.ts +++ b/api/src/service/domain/workflow/subproject_created.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; - -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -70,6 +70,8 @@ export function createEvent( subproject: InitialData, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating subproject_created event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/subproject_eventsourcing.ts b/api/src/service/domain/workflow/subproject_eventsourcing.ts index 8fd8cdd4a..9f510a35f 100644 --- a/api/src/service/domain/workflow/subproject_eventsourcing.ts +++ b/api/src/service/domain/workflow/subproject_eventsourcing.ts @@ -1,7 +1,7 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { EventSourcingError } from "../errors/event_sourcing_error"; @@ -16,6 +16,7 @@ import * as SubprojectProjectedBudgetUpdated from "./subproject_projected_budget import { SubprojectTraceEvent } from "./subproject_trace_event"; import * as SubprojectUpdated from "./subproject_updated"; import * as WorkflowitemsReordered from "./workflowitems_reordered"; +import logger from "lib/logger"; export function sourceSubprojects( ctx: Ctx, @@ -29,6 +30,7 @@ export function sourceSubprojects( const errors: Error[] = []; for (const event of events) { + logger.trace({ event }, "Validating subproject Event by applying it"); if (!event.type.startsWith("subproject_") && event.type !== "workflowitems_reordered") { continue; } diff --git a/api/src/service/domain/workflow/subproject_get.spec.ts b/api/src/service/domain/workflow/subproject_get.spec.ts index ae29eae01..e162a5f3d 100644 --- a/api/src/service/domain/workflow/subproject_get.spec.ts +++ b/api/src/service/domain/workflow/subproject_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/subproject_get.ts b/api/src/service/domain/workflow/subproject_get.ts index 3e2555463..4c66c65dc 100644 --- a/api/src/service/domain/workflow/subproject_get.ts +++ b/api/src/service/domain/workflow/subproject_get.ts @@ -1,5 +1,5 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -7,6 +7,7 @@ import { canAssumeIdentity } from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; import * as Subproject from "./subproject"; import { SubprojectTraceEvent } from "./subproject_trace_event"; +import logger from "lib/logger"; interface Repository { getSubproject(): Promise>; @@ -24,6 +25,7 @@ export async function getSubproject( return new NotFound(ctx, "subproject", subprojectId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intents: Intent[] = ["subproject.viewSummary", "subproject.viewDetails"]; if (!Subproject.permits(subproject, user, intents)) { diff --git a/api/src/service/domain/workflow/subproject_history_get.spec.ts b/api/src/service/domain/workflow/subproject_history_get.spec.ts index 360374623..e26a305b5 100644 --- a/api/src/service/domain/workflow/subproject_history_get.spec.ts +++ b/api/src/service/domain/workflow/subproject_history_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_history_get.ts b/api/src/service/domain/workflow/subproject_history_get.ts index f269a28ec..a044b4e49 100644 --- a/api/src/service/domain/workflow/subproject_history_get.ts +++ b/api/src/service/domain/workflow/subproject_history_get.ts @@ -1,5 +1,5 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -7,6 +7,7 @@ import { ServiceUser } from "../organization/service_user"; import { Filter, filterTraceEvents } from "./historyFilter"; import * as Subproject from "./subproject"; import { SubprojectTraceEvent } from "./subproject_trace_event"; +import logger from "lib/logger"; interface Repository { getSubproject(projectId, subprojectId): Promise>; @@ -26,10 +27,15 @@ export const getHistory = async ( return new NotFound(ctx, "subproject", subprojectId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intents: Intent[] = ["subproject.viewDetails", "subproject.viewHistory"]; - if (!(Subproject.permits(subproject, user, [intents[0]]) || - Subproject.permits(subproject, user, [intents[1]]))) { + if ( + !( + Subproject.permits(subproject, user, [intents[0]]) || + Subproject.permits(subproject, user, [intents[1]]) + ) + ) { return new NotAuthorized({ ctx, userId: user.id, intent: intents, target: subproject }); } } diff --git a/api/src/service/domain/workflow/subproject_list.spec.ts b/api/src/service/domain/workflow/subproject_list.spec.ts index 084f79fd3..e4aad98e4 100644 --- a/api/src/service/domain/workflow/subproject_list.spec.ts +++ b/api/src/service/domain/workflow/subproject_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { ServiceUser } from "../organization/service_user"; import { Permissions } from "../permissions"; diff --git a/api/src/service/domain/workflow/subproject_list.ts b/api/src/service/domain/workflow/subproject_list.ts index b8a904267..1cac846bd 100644 --- a/api/src/service/domain/workflow/subproject_list.ts +++ b/api/src/service/domain/workflow/subproject_list.ts @@ -1,11 +1,12 @@ import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { canAssumeIdentity } from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; import * as Subproject from "./subproject"; import { SubprojectTraceEvent } from "./subproject_trace_event"; +import logger from "lib/logger"; interface Repository { getAllSubprojects(): Promise>; @@ -23,6 +24,7 @@ export async function getAllVisible( } const allSubprojects = allSubprojectsResult; + logger.trace({ user }, "Checking user autorization"); const isVisible = user.id === "root" ? () => true diff --git a/api/src/service/domain/workflow/subproject_permission_grant.spec.ts b/api/src/service/domain/workflow/subproject_permission_grant.spec.ts index 200866766..5fb25aebf 100644 --- a/api/src/service/domain/workflow/subproject_permission_grant.spec.ts +++ b/api/src/service/domain/workflow/subproject_permission_grant.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_permission_grant.ts b/api/src/service/domain/workflow/subproject_permission_grant.ts index eb1698ce5..8de489fe4 100644 --- a/api/src/service/domain/workflow/subproject_permission_grant.ts +++ b/api/src/service/domain/workflow/subproject_permission_grant.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as Project from "./project"; import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as SubprojectPermissionGranted from "./subproject_permission_granted"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -37,7 +38,10 @@ export async function grantSubprojectPermission( return new NotFound(ctx, "subproject", subprojectId); } - // Create the new event: + logger.trace( + { issuer, grantee, intent, projectId, subprojectId }, + "Creating subproject_permission_granted event", + ); const permissionGranted = SubprojectPermissionGranted.createEvent( ctx.source, issuer.id, @@ -49,7 +53,8 @@ export async function grantSubprojectPermission( if (Result.isErr(permissionGranted)) { return new VError(permissionGranted, "failed to create permission granted event"); } - // Check authorization (if not root): + + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const grantIntent = "subproject.intent.grantPermission"; if (!Subproject.permits(subproject, issuer, [grantIntent])) { @@ -57,7 +62,7 @@ export async function grantSubprojectPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionGranted }, "Checking event validity"); const updatedSubproject = SubprojectEventSourcing.newSubprojectFromEvent( ctx, subproject, @@ -70,7 +75,7 @@ export async function grantSubprojectPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(subproject.permissions, updatedSubproject.permissions)) { return []; - } else { - return [permissionGranted]; } + + return [permissionGranted]; } diff --git a/api/src/service/domain/workflow/subproject_permission_granted.ts b/api/src/service/domain/workflow/subproject_permission_granted.ts index 32c91bd64..262c9386b 100644 --- a/api/src/service/domain/workflow/subproject_permission_granted.ts +++ b/api/src/service/domain/workflow/subproject_permission_granted.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import Intent, { subprojectIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; diff --git a/api/src/service/domain/workflow/subproject_permission_revoke.spec.ts b/api/src/service/domain/workflow/subproject_permission_revoke.spec.ts index 78967dad3..92326ea9d 100644 --- a/api/src/service/domain/workflow/subproject_permission_revoke.spec.ts +++ b/api/src/service/domain/workflow/subproject_permission_revoke.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_permission_revoke.ts b/api/src/service/domain/workflow/subproject_permission_revoke.ts index 7703945eb..94215f1bd 100644 --- a/api/src/service/domain/workflow/subproject_permission_revoke.ts +++ b/api/src/service/domain/workflow/subproject_permission_revoke.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import * as Project from "./project"; import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as SubprojectPermissionRevoked from "./subproject_permission_revoked"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -38,7 +39,10 @@ export async function revokeSubprojectPermission( return new NotFound(ctx, "subproject", subprojectId); } - // Create the new event: + logger.trace( + { issuer, revokee, intent, projectId, subprojectId }, + "Creating subproject_permission_revoked event", + ); const permissionRevoked = SubprojectPermissionRevoked.createEvent( ctx.source, issuer.id, @@ -50,7 +54,8 @@ export async function revokeSubprojectPermission( if (Result.isErr(permissionRevoked)) { return new VError(permissionRevoked, "failed to create permission revoked event"); } - // Check authorization (if not root): + + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const revokeIntent = "subproject.intent.revokePermission"; if (!Subproject.permits(subproject, issuer, [revokeIntent])) { @@ -63,7 +68,7 @@ export async function revokeSubprojectPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionRevoked }, "Checking event validity"); const updatedSubproject = SubprojectEventSourcing.newSubprojectFromEvent( ctx, subproject, @@ -73,13 +78,9 @@ export async function revokeSubprojectPermission( return new InvalidCommand(ctx, permissionRevoked, [updatedSubproject]); } - // Prevent revoking grant permission of last user + logger.trace("Prevent revoke grant permission on last user"); const intents: Intent[] = ["subproject.intent.grantPermission"]; - if ( - intent && - intents.includes(intent) && - subproject?.permissions[intent]?.length === 1 - ) { + if (intent && intents.includes(intent) && subproject?.permissions[intent]?.length === 1) { return new PreconditionError( ctx, permissionRevoked, @@ -90,7 +91,7 @@ export async function revokeSubprojectPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(subproject.permissions, updatedSubproject.permissions)) { return []; - } else { - return [permissionRevoked]; } + + return [permissionRevoked]; } diff --git a/api/src/service/domain/workflow/subproject_permission_revoked.ts b/api/src/service/domain/workflow/subproject_permission_revoked.ts index 0f4fd7b8c..5705151e7 100644 --- a/api/src/service/domain/workflow/subproject_permission_revoked.ts +++ b/api/src/service/domain/workflow/subproject_permission_revoked.ts @@ -1,6 +1,5 @@ import Joi = require("joi"); import { VError } from "verror"; - import Intent, { subprojectIntents } from "../../../authz/intents"; import * as Result from "../../../result"; import { Identity } from "../organization/identity"; diff --git a/api/src/service/domain/workflow/subproject_permissions_list.spec.ts b/api/src/service/domain/workflow/subproject_permissions_list.spec.ts index 5323a84af..5ea38d55b 100644 --- a/api/src/service/domain/workflow/subproject_permissions_list.spec.ts +++ b/api/src/service/domain/workflow/subproject_permissions_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/subproject_permissions_list.ts b/api/src/service/domain/workflow/subproject_permissions_list.ts index d8e411601..70eb37f6a 100644 --- a/api/src/service/domain/workflow/subproject_permissions_list.ts +++ b/api/src/service/domain/workflow/subproject_permissions_list.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -21,6 +22,8 @@ export async function getSubprojectPermissions( subprojectId: Subproject.Id, repository: Repository, ): Promise> { + logger.trace("Fetching subproject ..."); + const subprojectResult = await repository.getSubproject(projectId, subprojectId); if (Result.isErr(subprojectResult)) { @@ -29,6 +32,7 @@ export async function getSubprojectPermissions( const subproject: Subproject.Subproject = subprojectResult; + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intent = "subproject.intent.listPermissions"; if (!Subproject.permits(subproject, user, [intent])) { diff --git a/api/src/service/domain/workflow/subproject_projected_budget_delete.spec.ts b/api/src/service/domain/workflow/subproject_projected_budget_delete.spec.ts index bd9f6b90f..adfffb358 100644 --- a/api/src/service/domain/workflow/subproject_projected_budget_delete.spec.ts +++ b/api/src/service/domain/workflow/subproject_projected_budget_delete.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_projected_budget_delete.ts b/api/src/service/domain/workflow/subproject_projected_budget_delete.ts index 153361a85..7ed5ac5d7 100644 --- a/api/src/service/domain/workflow/subproject_projected_budget_delete.ts +++ b/api/src/service/domain/workflow/subproject_projected_budget_delete.ts @@ -1,6 +1,6 @@ import { isEqual } from "lodash"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import { ProjectedBudget } from "./projected_budget"; import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as SubprojectProjectedBudgetDeleted from "./subproject_projected_budget_deleted"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -38,7 +39,10 @@ export async function deleteProjectedBudget( return new NotFound(ctx, "subproject", subprojectId); } - // Create the new event: + logger.trace( + { issuer, projectId, subprojectId, organization }, + "Creating subproject_budget_deleted event", + ); const budgetDeleted = SubprojectProjectedBudgetDeleted.createEvent( ctx.source, issuer.id, @@ -50,7 +54,8 @@ export async function deleteProjectedBudget( if (Result.isErr(budgetDeleted)) { return new VError(budgetDeleted, "failed to create projected budget deleted event"); } - // Check authorization (if not root): + + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "subproject.budget.deleteProjected"; if (!Subproject.permits(subproject, issuer, [intent])) { @@ -58,7 +63,7 @@ export async function deleteProjectedBudget( } } - // Check that the new event is indeed valid: + logger.trace({ event: budgetDeleted }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent(ctx, subproject, budgetDeleted); if (Result.isErr(result)) { return new InvalidCommand(ctx, budgetDeleted, [result]); @@ -67,37 +72,40 @@ export async function deleteProjectedBudget( // Only emit the event if it causes any changes: if (isEqual(subproject.projectedBudgets, result.projectedBudgets)) { return { newEvents: [], projectedBudgets: result.projectedBudgets }; - } else { - // Create notification events: - const recipientsResult = subproject.assignee - ? await repository.getUsersForIdentity(subproject.assignee) - : []; - if (Result.isErr(recipientsResult)) { - return new VError(recipientsResult, `fetch users for ${subproject.assignee} failed`); - } - const notifications = recipientsResult.reduce((notifications, recipient) => { - // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { - const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - budgetDeleted, - projectId, - ); - if (Result.isErr(notification)) { - return new VError(notification, "failed to create event"); - } - notifications.push(notification); + } + + logger.trace("Creating notification events"); + const recipientsResult = subproject.assignee + ? await repository.getUsersForIdentity(subproject.assignee) + : []; + if (Result.isErr(recipientsResult)) { + return new VError(recipientsResult, `fetch users for ${subproject.assignee} failed`); + } + + const notifications = recipientsResult.reduce((notifications, recipient) => { + // The issuer doesn't receive a notification: + if (recipient !== issuer.id) { + const notification = NotificationCreated.createEvent( + ctx.source, + issuer.id, + recipient, + budgetDeleted, + projectId, + ); + if (Result.isErr(notification)) { + return new VError(notification, "failed to create event"); } - return notifications; - }, [] as NotificationCreated.Event[]); - if (Result.isErr(notifications)) { - return new VError(notifications, "failed to create notification events"); + notifications.push(notification); } - return { - newEvents: [budgetDeleted, ...notifications], - projectedBudgets: result.projectedBudgets, - }; + return notifications; + }, [] as NotificationCreated.Event[]); + + if (Result.isErr(notifications)) { + return new VError(notifications, "failed to create notification events"); } + + return { + newEvents: [budgetDeleted, ...notifications], + projectedBudgets: result.projectedBudgets, + }; } diff --git a/api/src/service/domain/workflow/subproject_projected_budget_update.spec.ts b/api/src/service/domain/workflow/subproject_projected_budget_update.spec.ts index dec3e330c..98fbf56d0 100644 --- a/api/src/service/domain/workflow/subproject_projected_budget_update.spec.ts +++ b/api/src/service/domain/workflow/subproject_projected_budget_update.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_projected_budget_update.ts b/api/src/service/domain/workflow/subproject_projected_budget_update.ts index f9904c444..bc04a7019 100644 --- a/api/src/service/domain/workflow/subproject_projected_budget_update.ts +++ b/api/src/service/domain/workflow/subproject_projected_budget_update.ts @@ -1,6 +1,6 @@ import { isEqual } from "lodash"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import { ProjectedBudget } from "./projected_budget"; import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as SubprojectProjectedBudgetUpdated from "./subproject_projected_budget_updated"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -38,7 +39,11 @@ export async function updateProjectedBudget( if (Result.isErr(subproject)) { return new NotFound(ctx, "subproject", subprojectId); } - // Create the new event: + + logger.trace( + { issuer, projectId, subprojectId, organization }, + "Creating subproject_budget_updated event", + ); const budgetUpdated = SubprojectProjectedBudgetUpdated.createEvent( ctx.source, issuer.id, @@ -51,7 +56,8 @@ export async function updateProjectedBudget( if (Result.isErr(budgetUpdated)) { return new VError(budgetUpdated, "failed to create projected budget updated event"); } - // Check authorization (if not root): + + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "subproject.budget.updateProjected"; if (!Subproject.permits(subproject, issuer, [intent])) { @@ -59,7 +65,7 @@ export async function updateProjectedBudget( } } - // Check that the new event is indeed valid: + logger.trace({ event: budgetUpdated }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent(ctx, subproject, budgetUpdated); if (Result.isErr(result)) { return new InvalidCommand(ctx, budgetUpdated, [result]); @@ -68,37 +74,39 @@ export async function updateProjectedBudget( // Only emit the event if it causes any changes: if (isEqual(subproject.projectedBudgets, result.projectedBudgets)) { return { newEvents: [], projectedBudgets: result.projectedBudgets }; - } else { - // Create notification events: - const recipientsResult = subproject.assignee - ? await repository.getUsersForIdentity(subproject.assignee) - : []; - if (Result.isErr(recipientsResult)) { - return new VError(recipientsResult, `fetch users for ${subproject.assignee} failed`); - } - const notifications = recipientsResult.reduce((notifications, recipient) => { - // The issuer doesn't receive a notification: - if (recipient !== issuer.id) { - const notification = NotificationCreated.createEvent( - ctx.source, - issuer.id, - recipient, - budgetUpdated, - projectId, - ); - if (Result.isErr(notification)) { - return new VError(notification, "failed to create event"); - } - notifications.push(notification); + } + + logger.trace("Creating notification events"); + const recipientsResult = subproject.assignee + ? await repository.getUsersForIdentity(subproject.assignee) + : []; + if (Result.isErr(recipientsResult)) { + return new VError(recipientsResult, `fetch users for ${subproject.assignee} failed`); + } + const notifications = recipientsResult.reduce((notifications, recipient) => { + // The issuer doesn't receive a notification: + if (recipient !== issuer.id) { + const notification = NotificationCreated.createEvent( + ctx.source, + issuer.id, + recipient, + budgetUpdated, + projectId, + ); + if (Result.isErr(notification)) { + return new VError(notification, "failed to create event"); } - return notifications; - }, [] as NotificationCreated.Event[]); - if (Result.isErr(notifications)) { - return new VError(notifications, "failed to create notification events"); + notifications.push(notification); } - return { - newEvents: [budgetUpdated, ...notifications], - projectedBudgets: result.projectedBudgets, - }; + return notifications; + }, [] as NotificationCreated.Event[]); + + if (Result.isErr(notifications)) { + return new VError(notifications, "failed to create notification events"); } + + return { + newEvents: [budgetUpdated, ...notifications], + projectedBudgets: result.projectedBudgets, + }; } diff --git a/api/src/service/domain/workflow/subproject_update.spec.ts b/api/src/service/domain/workflow/subproject_update.spec.ts index 799e48743..a28aed8b7 100644 --- a/api/src/service/domain/workflow/subproject_update.spec.ts +++ b/api/src/service/domain/workflow/subproject_update.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/subproject_update.ts b/api/src/service/domain/workflow/subproject_update.ts index 161f51dc1..1a00dacc3 100644 --- a/api/src/service/domain/workflow/subproject_update.ts +++ b/api/src/service/domain/workflow/subproject_update.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); import isEqual = require("lodash.isequal"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -15,6 +15,7 @@ import * as Project from "./project"; import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as SubprojectUpdated from "./subproject_updated"; +import logger from "lib/logger"; export type RequestData = SubprojectUpdated.UpdatedData; export const requestDataSchema = SubprojectUpdated.updatedDataSchema; @@ -46,7 +47,7 @@ export async function updateSubproject( return new NotFound(ctx, "subproject", subprojectId); } - // Create the new event: + logger.trace({ issuer, data }, "Creating subproject_updated event"); const subprojectUpdatedResult = SubprojectUpdated.createEvent( ctx.source, issuer.id, @@ -59,7 +60,7 @@ export async function updateSubproject( } const subprojectUpdated = subprojectUpdatedResult; - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "subproject.update"; if (!Subproject.permits(subproject, issuer, [intent])) { @@ -67,7 +68,7 @@ export async function updateSubproject( } } - // Check that the new event is indeed valid: + logger.trace({ event: subprojectUpdated }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent(ctx, subproject, subprojectUpdated); if (Result.isErr(result)) { return new InvalidCommand(ctx, subprojectUpdated, [result]); @@ -78,7 +79,7 @@ export async function updateSubproject( return []; } - // Create notification events: + logger.trace("Creating notification events"); let notifications: Result.Type = []; if (subproject.assignee !== undefined) { const recipientsResult = await repository.getUsersForIdentity(subproject.assignee); @@ -103,9 +104,11 @@ export async function updateSubproject( return notifications; }, [] as NotificationCreated.Event[]); } + if (Result.isErr(notifications)) { return new VError(notifications, "failed to create notification events"); } + return [subprojectUpdated, ...notifications]; } diff --git a/api/src/service/domain/workflow/user_assignment_get.spec.ts b/api/src/service/domain/workflow/user_assignment_get.spec.ts index dc39aa0a6..a4a8922f0 100644 --- a/api/src/service/domain/workflow/user_assignment_get.spec.ts +++ b/api/src/service/domain/workflow/user_assignment_get.spec.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { Identity } from "../organization/identity"; diff --git a/api/src/service/domain/workflow/user_assignments_get.ts b/api/src/service/domain/workflow/user_assignments_get.ts index 75ffd5687..e8f8d2f52 100644 --- a/api/src/service/domain/workflow/user_assignments_get.ts +++ b/api/src/service/domain/workflow/user_assignments_get.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { isEmpty } from "../../../lib/emptyChecks"; +import { isEmpty } from "lib/emptyChecks"; import * as Project from "./project"; import * as Subproject from "./subproject"; import * as Workflowitem from "./workflowitem"; @@ -9,7 +9,8 @@ import * as UserRecord from "../organization/user_record"; import Intent from "../../../authz/intents"; import { ServiceUser } from "../organization/service_user"; import { NotAuthorized } from "../errors/not_authorized"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; export interface RequestData { userId: string; @@ -58,7 +59,10 @@ export async function getUserAssignments( } const user = userResult; - // Check if revokee and issuer belong to the same organization + logger.trace( + { user, issuer }, + "Checking that revokee and issuer belong to the same organization", + ); if (user.organization !== issuerOrganization) { return new NotAuthorized({ ctx, @@ -78,9 +82,9 @@ export async function getUserAssignments( return new VError(error, "failed to fetch projects"); } - // Crawling through all open projects, subprojects and workflowitems: for await (const project of projects) { if (project.status === "closed") continue; + logger.trace({ project }, "Looking for user assigments in projects"); if (project.assignee === userId) { if (!isRoot && !Project.permits(project, issuer, projectIntents)) { hiddenAssignments.hasHiddenProjects = true; @@ -93,6 +97,7 @@ export async function getUserAssignments( for await (const subproject of subprojects) { if (subproject.status === "closed") continue; + logger.trace({ subproject }, "Looking for user assignments in subprojects"); if (subproject.assignee === userId) { if (!isRoot && !Subproject.permits(subproject, issuer, subprojectIntents)) { hiddenAssignments.hasHiddenSubprojects = true; @@ -105,6 +110,7 @@ export async function getUserAssignments( for await (const workflowitem of workflowitems) { if (workflowitem.status === "closed") continue; + logger.trace({ workflowitem }, "Looking for user assignments in workflowitems"); if (workflowitem.assignee === userId) { if (!isRoot && !Workflowitem.permits(workflowitem, issuer, workflowitemIntents)) { hiddenAssignments.hasHiddenWorkflowitems = true; diff --git a/api/src/service/domain/workflow/workflowitem_assign.spec.ts b/api/src/service/domain/workflow/workflowitem_assign.spec.ts index 4bbfa3a56..212c89393 100644 --- a/api/src/service/domain/workflow/workflowitem_assign.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_assign.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/workflowitem_assign.ts b/api/src/service/domain/workflow/workflowitem_assign.ts index ae280c167..94aaa8c05 100644 --- a/api/src/service/domain/workflow/workflowitem_assign.ts +++ b/api/src/service/domain/workflow/workflowitem_assign.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -14,6 +14,7 @@ import * as Subproject from "./subproject"; import * as Workflowitem from "./workflowitem"; import * as WorkflowitemAssigned from "./workflowitem_assigned"; import * as WorkflowitemEventSourcing from "./workflowitem_eventsourcing"; +import logger from "lib/logger"; interface Repository { getWorkflowitem(workflowitemId: string): Promise>; @@ -43,6 +44,10 @@ export async function assignWorkflowitem( return { newEvents: [], workflowitem }; } + logger.trace( + { publisher, assignee, projectId, subprojectId, workflowitemId }, + "Creating workflowitem_assigned event", + ); const assignEvent = WorkflowitemAssigned.createEvent( ctx.source, publisher.id, @@ -56,7 +61,7 @@ export async function assignWorkflowitem( return new VError(assignEvent, "failed to create event"); } - // Check authorization: + logger.trace({ publisher }, "Checking user authorization"); if (publisher.id !== "root") { const assignIntent = "workflowitem.assign"; if (!Workflowitem.permits(workflowitem, publisher, [assignIntent])) { @@ -69,13 +74,13 @@ export async function assignWorkflowitem( } } - // Check that the new event is indeed valid: + logger.trace({ event: assignEvent }, "Checking event validity"); const result = WorkflowitemEventSourcing.newWorkflowitemFromEvent(ctx, workflowitem, assignEvent); if (Result.isErr(result)) { return new InvalidCommand(ctx, assignEvent, [result]); } - // Create notification events: + logger.trace("Creating notification events"); const recipientsResult = await repository.getUsersForIdentity(assignee); if (Result.isErr(recipientsResult)) { return new VError(recipientsResult, `fetch users for ${assignee} failed`); diff --git a/api/src/service/domain/workflow/workflowitem_close.spec.ts b/api/src/service/domain/workflow/workflowitem_close.spec.ts index f324e5925..e0e868bc1 100644 --- a/api/src/service/domain/workflow/workflowitem_close.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_close.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/workflowitem_close.ts b/api/src/service/domain/workflow/workflowitem_close.ts index d3b00de07..0543d7609 100644 --- a/api/src/service/domain/workflow/workflowitem_close.ts +++ b/api/src/service/domain/workflow/workflowitem_close.ts @@ -1,5 +1,5 @@ import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -16,6 +16,7 @@ import { Id } from "./workflowitem"; import * as WorkflowitemClosed from "./workflowitem_closed"; import * as WorkflowitemEventSourcing from "./workflowitem_eventsourcing"; import { sortWorkflowitems } from "./workflowitem_ordering"; +import logger from "lib/logger"; interface Repository { getWorkflowitems( @@ -67,6 +68,10 @@ export async function closeWorkflowitem( return []; } + logger.trace( + { closingUser, projectId, subprojectId, workflowitemId }, + "Creating workflowitem_closed event", + ); const publisher = closingUser.id; const closeEvent = WorkflowitemClosed.createEvent( ctx.source, @@ -92,7 +97,7 @@ export async function closeWorkflowitem( } const assignedIdentities = assignedIdentitiesResult; - // Check if user is allowed to close the workflowitem + logger.trace({ closingUser }, "Checking user authorizaion"); if (closingUser.id !== "root") { if (subproject.validator !== undefined && subproject.validator !== closingUser.id) { return new PreconditionError( @@ -109,7 +114,7 @@ export async function closeWorkflowitem( } } - // Make sure all previous items (wrt. the ordering) are already closed: + logger.trace("Making sure all previous workflowitems were already closed"); for (const item of sortedWorkflowitems) { if (item.id === workflowitemId) { break; @@ -119,7 +124,7 @@ export async function closeWorkflowitem( } } - // Check that the new event is indeed valid: + logger.trace({ event: closeEvent }, "Checking event validity"); const result = WorkflowitemEventSourcing.newWorkflowitemFromEvent( ctx, workflowitemToClose, @@ -129,7 +134,7 @@ export async function closeWorkflowitem( return new InvalidCommand(ctx, closeEvent, [result]); } - // Create notification events: + logger.trace("Creating notification events"); const notifications: Result.Type = assignedIdentities.reduce( (notifications, recipient) => { // The issuer doesn't receive a notification: diff --git a/api/src/service/domain/workflow/workflowitem_create.spec.ts b/api/src/service/domain/workflow/workflowitem_create.spec.ts index 5f3f71536..1fb508643 100644 --- a/api/src/service/domain/workflow/workflowitem_create.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_create.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { PreconditionError } from "../errors/precondition_error"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/workflowitem_create.ts b/api/src/service/domain/workflow/workflowitem_create.ts index 15d6c867d..5eb42c0e0 100644 --- a/api/src/service/domain/workflow/workflowitem_create.ts +++ b/api/src/service/domain/workflow/workflowitem_create.ts @@ -1,11 +1,21 @@ import Joi = require("joi"); +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; import Intent, { workflowitemIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { config } from "../../../config"; import * as Result from "../../../result"; import { randomString } from "../../hash"; import * as AdditionalData from "../additional_data"; import { BusinessEvent } from "../business_event"; +import { + GenericDocument, + hashDocument, + StoredDocument, + UploadedDocument, + uploadedDocumentSchema, +} from "../document/document"; +import * as WorkflowitemDocumentUploaded from "../document/workflowitem_document_uploaded"; import { AlreadyExists } from "../errors/already_exists"; import { InvalidCommand } from "../errors/invalid_command"; import { NotAuthorized } from "../errors/not_authorized"; @@ -13,19 +23,10 @@ import { PreconditionError } from "../errors/precondition_error"; import { ServiceUser } from "../organization/service_user"; import { Permissions } from "../permissions"; import Type, { workflowitemTypeSchema } from "../workflowitem_types/types"; -import { config } from "../../../config"; -import { - hashDocument, - StoredDocument, - UploadedDocument, - uploadedDocumentSchema, -} from "../document/document"; import * as Project from "./project"; import * as Subproject from "./subproject"; import * as Workflowitem from "./workflowitem"; import * as WorkflowitemCreated from "./workflowitem_created"; -import * as WorkflowitemDocumentUploaded from "../document/workflowitem_document_uploaded"; -import { GenericDocument } from "../document/document"; import uuid = require("uuid"); export interface RequestData { @@ -119,6 +120,10 @@ export async function createWorkflowitem( const documentUploadedEvents: BusinessEvent[] = []; if (reqData.documents) { + logger.trace( + { req: reqData }, + "Trying to hash documents in preparation for workflowitem_created event", + ); const existingDocuments = await repository.getAllDocuments(); if (Result.isErr(existingDocuments)) { return new VError(existingDocuments, "cannot get documents"); @@ -137,8 +142,13 @@ export async function createWorkflowitem( // upload documents to storage service // generate document events (document_uploaded, secret_published) const documentUploadedEventsResults: Result.Type[] = await Promise.all( - reqData.documents.map(async (d) => { - return repository.uploadDocumentToStorageService(d.fileName || "", d.base64, d.id); + reqData.documents.map(async (document) => { + logger.trace({ document }, "Trying to upload document to storage service"); + return repository.uploadDocumentToStorageService( + document.fileName || "", + document.base64, + document.id, + ); }), ); for (const result of documentUploadedEventsResults) { @@ -161,6 +171,10 @@ export async function createWorkflowitem( id: d.id, }; + logger.trace( + { req: reqData }, + "Trying to create 'WorkflowitemDocumentUploaded' Event from request data", + ); const workflowitemEvent = WorkflowitemDocumentUploaded.createEvent( ctx.source, publisher, @@ -189,6 +203,7 @@ export async function createWorkflowitem( } } + logger.trace({ req: reqData }, "Trying to create 'WorkflowitemCreated' Event from request data"); const workflowitemCreated = WorkflowitemCreated.createEvent( ctx.source, publisher, @@ -216,7 +231,7 @@ export async function createWorkflowitem( return new VError(workflowitemCreated, "failed to create workflowitem created event"); } - // Check if workflowitemId already exists + logger.trace({ req: reqData }, "Check if Workflowitem exists"); if ( await repository.workflowitemExists( reqData.projectId, @@ -233,7 +248,7 @@ export async function createWorkflowitem( } const subproject = subprojectResult; - // Check authorization + logger.trace({ user: creatingUser }, "Checking if user is authorized"); if (creatingUser.id === "root") { return new PreconditionError( ctx, @@ -246,13 +261,13 @@ export async function createWorkflowitem( return new NotAuthorized({ ctx, userId: creatingUser.id, intent, target: subproject }); } - // Check that the event is valid: + logger.trace({ event: workflowitemCreated }, "Checking if Event is valid"); const result = WorkflowitemCreated.createFrom(ctx, workflowitemCreated); if (Result.isErr(result)) { return new InvalidCommand(ctx, workflowitemCreated, [result]); } - // Check the workflowitem type + logger.trace({ subproject }, "Checking the workflowitem type"); if ( subproject.workflowitemType !== undefined && workflowitemCreated.workflowitem.workflowitemType !== subproject.workflowitemType diff --git a/api/src/service/domain/workflow/workflowitem_created.ts b/api/src/service/domain/workflow/workflowitem_created.ts index 9cef1e33c..4fd373bc7 100644 --- a/api/src/service/domain/workflow/workflowitem_created.ts +++ b/api/src/service/domain/workflow/workflowitem_created.ts @@ -1,7 +1,7 @@ import Joi = require("joi"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; import { EventSourcingError } from "../errors/event_sourcing_error"; diff --git a/api/src/service/domain/workflow/workflowitem_eventsourcing.ts b/api/src/service/domain/workflow/workflowitem_eventsourcing.ts index 094ef8278..94369039d 100644 --- a/api/src/service/domain/workflow/workflowitem_eventsourcing.ts +++ b/api/src/service/domain/workflow/workflowitem_eventsourcing.ts @@ -1,8 +1,10 @@ +import { Ctx } from "lib/ctx"; +import deepcopy from "lib/deepcopy"; +import logger from "lib/logger"; import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; -import deepcopy from "../../../lib/deepcopy"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; +import { mapOldDocToNewDoc, StoredDocument } from "../document/document"; import * as DocumentValidated from "../document/document_validated"; import { EventSourcingError } from "../errors/event_sourcing_error"; import * as Workflowitem from "./workflowitem"; @@ -13,7 +15,6 @@ import * as WorkflowitemPermissionGranted from "./workflowitem_permission_grante import * as WorkflowitemPermissionRevoked from "./workflowitem_permission_revoked"; import { WorkflowitemTraceEvent } from "./workflowitem_trace_event"; import * as WorkflowitemUpdated from "./workflowitem_updated"; -import { mapOldDocToNewDoc, StoredDocument } from "../document/document"; export function sourceWorkflowitems( ctx: Ctx, @@ -27,6 +28,7 @@ export function sourceWorkflowitems( const errors: Error[] = []; for (const event of events) { + logger.trace({ event }, "Validating workflowitem event by applying it"); if (!event.type.startsWith("workflowitem_")) { continue; } diff --git a/api/src/service/domain/workflow/workflowitem_get.spec.ts b/api/src/service/domain/workflow/workflowitem_get.spec.ts index 842366395..fbeacb953 100644 --- a/api/src/service/domain/workflow/workflowitem_get.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/workflowitem_get.ts b/api/src/service/domain/workflow/workflowitem_get.ts index bce227bfe..5c9f15879 100644 --- a/api/src/service/domain/workflow/workflowitem_get.ts +++ b/api/src/service/domain/workflow/workflowitem_get.ts @@ -1,5 +1,5 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -7,6 +7,7 @@ import { canAssumeIdentity } from "../organization/auth_token"; import { ServiceUser } from "../organization/service_user"; import * as Workflowitem from "./workflowitem"; import { WorkflowitemTraceEvent } from "./workflowitem_trace_event"; +import logger from "lib/logger"; interface Repository { getWorkflowitem(): Promise>; @@ -19,10 +20,12 @@ export async function getWorkflowitem( repository: Repository, ): Promise> { const workflowitem = await repository.getWorkflowitem(); + if (Result.isErr(workflowitem)) { return new NotFound(ctx, "workflowitem", workflowitemId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intent = "workflowitem.view"; if (!Workflowitem.permits(workflowitem, user, [intent])) { diff --git a/api/src/service/domain/workflow/workflowitem_get_details.spec.ts b/api/src/service/domain/workflow/workflowitem_get_details.spec.ts index a683384e9..200c48c19 100644 --- a/api/src/service/domain/workflow/workflowitem_get_details.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_get_details.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/workflowitem_get_details.ts b/api/src/service/domain/workflow/workflowitem_get_details.ts index 8db4caa29..9398c5eb2 100644 --- a/api/src/service/domain/workflow/workflowitem_get_details.ts +++ b/api/src/service/domain/workflow/workflowitem_get_details.ts @@ -1,11 +1,12 @@ import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; import { ServiceUser } from "../organization/service_user"; import * as Workflowitem from "./workflowitem"; import * as WorkflowitemDocument from "../document/document"; +import logger from "lib/logger"; interface Repository { getWorkflowitem(): Promise>; @@ -24,6 +25,7 @@ export async function getWorkflowitemDetails( return new NotFound(ctx, "workflowitem", workflowitemId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intent = "workflowitem.view"; if (!Workflowitem.permits(workflowitem, user, [intent])) { diff --git a/api/src/service/domain/workflow/workflowitem_history_get.spec.ts b/api/src/service/domain/workflow/workflowitem_history_get.spec.ts index 353478118..d81f79ccb 100644 --- a/api/src/service/domain/workflow/workflowitem_history_get.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_history_get.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/workflowitem_history_get.ts b/api/src/service/domain/workflow/workflowitem_history_get.ts index 834e65bf9..229bd0e78 100644 --- a/api/src/service/domain/workflow/workflowitem_history_get.ts +++ b/api/src/service/domain/workflow/workflowitem_history_get.ts @@ -1,5 +1,6 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -25,17 +26,24 @@ export const getHistory = async ( repository: Repository, filter?: Filter, ): Promise> => { + logger.trace("Fetching workflowitem history ..."); + const workflowitem = await repository.getWorkflowitem(projectId, subprojectId, workflowitemId); if (Result.isErr(workflowitem)) { return new NotFound(ctx, "workflowitem", workflowitemId); } + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { - const intents: Intent[] = ["workflowitem.view", "workflowitem.viewHistory" ]; - if (!(Workflowitem.permits(workflowitem, user, [intents[0]]) || - Workflowitem.permits(workflowitem, user, [intents[1] ])) ) { - return new NotAuthorized({ ctx, userId: user.id, intent: intents, target: workflowitem }); + const intents: Intent[] = ["workflowitem.view", "workflowitem.viewHistory"]; + if ( + !( + Workflowitem.permits(workflowitem, user, [intents[0]]) || + Workflowitem.permits(workflowitem, user, [intents[1]]) + ) + ) { + return new NotAuthorized({ ctx, userId: user.id, intent: intents, target: workflowitem }); } } diff --git a/api/src/service/domain/workflow/workflowitem_list.spec.ts b/api/src/service/domain/workflow/workflowitem_list.spec.ts index 60c5c2857..31a1f50be 100644 --- a/api/src/service/domain/workflow/workflowitem_list.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotFound } from "../errors/not_found"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/workflowitem_list.ts b/api/src/service/domain/workflow/workflowitem_list.ts index c105af970..f720965bd 100644 --- a/api/src/service/domain/workflow/workflowitem_list.ts +++ b/api/src/service/domain/workflow/workflowitem_list.ts @@ -1,7 +1,7 @@ +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import { VError } from "verror"; - import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; import * as Result from "../../../result"; import { NotFound } from "../errors/not_found"; import { canAssumeIdentity } from "../organization/auth_token"; @@ -42,17 +42,19 @@ export async function getAllVisible( ); } + logger.trace("Sorting workflowitems ..."); const sortedWorkflowitems = sortWorkflowitems(workflowitems, workflowitemOrdering); + logger.trace("Redact workflowitems the user is not authorized to see"); const visibleWorkflowitems = sortedWorkflowitems // Redact workflowitems the user is not entitled to see: - .map(item => + .map((item) => user.id === "root" || Workflowitem.permits(item, user, ["workflowitem.view"]) ? item : Workflowitem.redact(item), ) // Only keep history event the user may see and remove all others: - .map(item => (item.isRedacted ? item : { ...item, log: traceEventsVisibleTo(item, user) })); + .map((item) => (item.isRedacted ? item : { ...item, log: traceEventsVisibleTo(item, user) })); return visibleWorkflowitems; } @@ -70,7 +72,7 @@ const requiredPermissions = new Map([ function traceEventsVisibleTo(workflowitem: Workflowitem.Workflowitem, user: ServiceUser) { const traceEvents = workflowitem.log; - return traceEvents.filter(traceEvent => { + return traceEvents.filter((traceEvent) => { if (user.id === "root") { return true; } @@ -87,6 +89,6 @@ function traceEventsVisibleTo(workflowitem: Workflowitem.Workflowitem, user: Ser ), ); - return [...eligibleIdentities.values()].some(identity => canAssumeIdentity(user, identity)); + return [...eligibleIdentities.values()].some((identity) => canAssumeIdentity(user, identity)); }); } diff --git a/api/src/service/domain/workflow/workflowitem_ordering.spec.ts b/api/src/service/domain/workflow/workflowitem_ordering.spec.ts index 6b7724a3e..fb3c7f7f7 100644 --- a/api/src/service/domain/workflow/workflowitem_ordering.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_ordering.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/domain/workflow/workflowitem_ordering.ts b/api/src/service/domain/workflow/workflowitem_ordering.ts index db6d3a54b..48eb66ba4 100644 --- a/api/src/service/domain/workflow/workflowitem_ordering.ts +++ b/api/src/service/domain/workflow/workflowitem_ordering.ts @@ -1,6 +1,5 @@ import { AssertionError } from "assert"; import * as Workflowitem from "./workflowitem"; -import * as WorkflowitemClosed from "./workflowitem_closed"; export type WorkflowitemOrdering = Workflowitem.Id[]; @@ -10,8 +9,8 @@ interface ItemAndIndex { } export function sortWorkflowitems< - T extends Workflowitem.ScrubbedWorkflowitem | Workflowitem.Workflowitem, ->(workflowitems: T[], ordering: string[]): T[] { + T extends Workflowitem.ScrubbedWorkflowitem + | Workflowitem.Workflowitem>(workflowitems: T[], ordering: string[]): T[] { // The index is needed to enable stable sorting: const items = workflowitems.map((workflowitem, index) => ({ index, workflowitem })); @@ -19,7 +18,7 @@ export function sortWorkflowitems< items.sort((a, b) => byOrderingCriteria(a, b, ordering)); // Return the sorted items: - return items.map(item => item.workflowitem); + return items.map((item) => item.workflowitem); } /** @@ -71,7 +70,7 @@ function isClosed(item: Workflowitem.ScrubbedWorkflowitem): boolean { } function closedAt(item: Workflowitem.ScrubbedWorkflowitem): any { - const traceEvent = item.log.find(e => e.businessEvent.type === "workflowitem_closed"); + const traceEvent = item.log.find((e) => e.businessEvent.type === "workflowitem_closed"); if (traceEvent === undefined || traceEvent.businessEvent.type !== "workflowitem_closed") { return new AssertionError({ message: `Expected close event for workflowitem ${item.id}` }); diff --git a/api/src/service/domain/workflow/workflowitem_permission_grant.spec.ts b/api/src/service/domain/workflow/workflowitem_permission_grant.spec.ts index 54ee0c1c3..639c4fef8 100644 --- a/api/src/service/domain/workflow/workflowitem_permission_grant.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_permission_grant.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/workflowitem_permission_grant.ts b/api/src/service/domain/workflow/workflowitem_permission_grant.ts index 97bfa4eae..b27bea240 100644 --- a/api/src/service/domain/workflow/workflowitem_permission_grant.ts +++ b/api/src/service/domain/workflow/workflowitem_permission_grant.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -18,6 +18,7 @@ import * as Workflowitem from "./workflowitem"; import * as WorkflowitemEventSourcing from "./workflowitem_eventsourcing"; import * as WorkflowitemPermissionGranted from "./workflowitem_permission_granted"; import { config } from "../../../config"; +import logger from "lib/logger"; interface Repository { getWorkflowitem( @@ -47,7 +48,10 @@ export async function grantWorkflowitemPermission( return new NotFound(ctx, "workflowitem", workflowitemId); } - // Create the new event: + logger.trace( + { issuer, grantee, intent, projectId, subprojectId, workflowitemId }, + "Creating workflowitem_permission_grantet event", + ); const permissionGrantedEventResult = WorkflowitemPermissionGranted.createEvent( ctx.source, issuer.id, @@ -62,6 +66,7 @@ export async function grantWorkflowitemPermission( } const permissionGrantedEvent = permissionGrantedEventResult; + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const grantIntent = "workflowitem.intent.grantPermission"; if (!Workflowitem.permits(workflowitem, issuer, [grantIntent])) { @@ -74,7 +79,7 @@ export async function grantWorkflowitemPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionGrantedEvent }, "Checking event validity"); const updatedWorkflowitem = WorkflowitemEventSourcing.newWorkflowitemFromEvent( ctx, workflowitem, @@ -84,7 +89,7 @@ export async function grantWorkflowitemPermission( return new InvalidCommand(ctx, permissionGrantedEvent, [updatedWorkflowitem]); } - // Check document access for users of new organizations + logger.trace("Check document access for users of new organizations"); const { documents } = workflowitem; const documentEvents: BusinessEvent[] = []; @@ -94,7 +99,7 @@ export async function grantWorkflowitemPermission( return new VError(organizations, "failed to get organization for sharing documents"); } - // share all documents with all organizations + logger.trace("Share all documents with all organizations"); for (const organization of organizations) { for (const doc of documents) { const shareDocumentEventResult = await repository.shareDocument(doc.id, organization); @@ -114,9 +119,9 @@ export async function grantWorkflowitemPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(workflowitem.permissions, updatedWorkflowitem.permissions)) { return []; - } else { - return [permissionGrantedEvent, ...documentEvents]; } + + return [permissionGrantedEvent, ...documentEvents]; } async function getOrganizations( diff --git a/api/src/service/domain/workflow/workflowitem_permission_revoke.spec.ts b/api/src/service/domain/workflow/workflowitem_permission_revoke.spec.ts index beaee8363..c9583b44c 100644 --- a/api/src/service/domain/workflow/workflowitem_permission_revoke.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_permission_revoke.spec.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/workflowitem_permission_revoke.ts b/api/src/service/domain/workflow/workflowitem_permission_revoke.ts index c60d5fc1b..9b7aa0715 100644 --- a/api/src/service/domain/workflow/workflowitem_permission_revoke.ts +++ b/api/src/service/domain/workflow/workflowitem_permission_revoke.ts @@ -2,7 +2,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; import Intent from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -16,6 +16,7 @@ import * as Subproject from "./subproject"; import * as Workflowitem from "./workflowitem"; import * as WorkflowitemEventSourcing from "./workflowitem_eventsourcing"; import * as WorkflowitemPermissionRevoked from "./workflowitem_permission_revoked"; +import logger from "lib/logger"; interface Repository { getWorkflowitem( @@ -41,7 +42,10 @@ export async function revokeWorkflowitemPermission( return new NotFound(ctx, "workflowitem", workflowitemId); } - // Create the new event: + logger.trace( + { issuer, revokee, intent, projectId, subprojectId, workflowitemId }, + "Creating workflowitem_permission_revoked event", + ); const permissionRevoked = WorkflowitemPermissionRevoked.createEvent( ctx.source, issuer.id, @@ -55,7 +59,7 @@ export async function revokeWorkflowitemPermission( return new VError(permissionRevoked, "failed to create permission revoked event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const revokeIntent = "workflowitem.intent.revokePermission"; if (!Workflowitem.permits(workflowitem, issuer, [revokeIntent])) { @@ -68,7 +72,7 @@ export async function revokeWorkflowitemPermission( } } - // Check that the new event is indeed valid: + logger.trace({ event: permissionRevoked }, "Checking event validity"); const updatedWorkflowitem = WorkflowitemEventSourcing.newWorkflowitemFromEvent( ctx, workflowitem, @@ -78,13 +82,9 @@ export async function revokeWorkflowitemPermission( return new InvalidCommand(ctx, permissionRevoked, [updatedWorkflowitem]); } - // Prevent revoking grant permission of last user + logger.trace("Prevent revoke grant permission on last user"); const intents: Intent[] = ["workflowitem.intent.grantPermission"]; - if ( - intent && - intents.includes(intent) && - workflowitem?.permissions[intent]?.length === 1 - ) { + if (intent && intents.includes(intent) && workflowitem?.permissions[intent]?.length === 1) { return new PreconditionError( ctx, permissionRevoked, @@ -95,7 +95,7 @@ export async function revokeWorkflowitemPermission( // Only emit the event if it causes any changes to the permissions: if (isEqual(workflowitem.permissions, updatedWorkflowitem.permissions)) { return []; - } else { - return [permissionRevoked]; } + + return [permissionRevoked]; } diff --git a/api/src/service/domain/workflow/workflowitem_permissions_list.spec.ts b/api/src/service/domain/workflow/workflowitem_permissions_list.spec.ts index 5c1cdbfba..784458067 100644 --- a/api/src/service/domain/workflow/workflowitem_permissions_list.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_permissions_list.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; diff --git a/api/src/service/domain/workflow/workflowitem_permissions_list.ts b/api/src/service/domain/workflow/workflowitem_permissions_list.ts index e8f27bc46..c80be3a5b 100644 --- a/api/src/service/domain/workflow/workflowitem_permissions_list.ts +++ b/api/src/service/domain/workflow/workflowitem_permissions_list.ts @@ -1,4 +1,5 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; +import logger from "lib/logger"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { NotFound } from "../errors/not_found"; @@ -31,6 +32,7 @@ export async function getAll( } const workflowitem: Workflowitem.Workflowitem = result; + logger.trace({ user }, "Checking user authorization"); if (user.id !== "root") { const intent = "workflowitem.intent.listPermissions"; if (!Workflowitem.permits(workflowitem, user, [intent])) { diff --git a/api/src/service/domain/workflow/workflowitem_update.spec.ts b/api/src/service/domain/workflow/workflowitem_update.spec.ts index 23d2c55bb..c37f7b6e9 100644 --- a/api/src/service/domain/workflow/workflowitem_update.spec.ts +++ b/api/src/service/domain/workflow/workflowitem_update.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflow/workflowitem_update.ts b/api/src/service/domain/workflow/workflowitem_update.ts index d93fe1b33..627df5595 100644 --- a/api/src/service/domain/workflow/workflowitem_update.ts +++ b/api/src/service/domain/workflow/workflowitem_update.ts @@ -1,6 +1,6 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -24,6 +24,7 @@ import * as WorkflowitemDocumentUploaded from "../document/workflowitem_document import * as WorkflowitemEventSourcing from "./workflowitem_eventsourcing"; import * as WorkflowitemUpdated from "./workflowitem_updated"; import uuid = require("uuid"); +import logger from "lib/logger"; export interface RequestData { displayName?: string; @@ -61,7 +62,7 @@ function docIdAlreadyExists(allDocuments: GenericDocument[], docId: string) { } function generateUniqueDocId(allDocuments: GenericDocument[]): string { - // Generate a new document id + logger.trace("Generation unique document id"); while (true) { const docId = uuid.v4(); if (!docIdAlreadyExists(allDocuments, docId)) { @@ -93,6 +94,7 @@ export async function updateWorkflowitem( return new VError(existingDocuments, "cannot get documents"); } + logger.trace("Preparing workflowitem_updated event"); // preparation for workflowitem_updated event for (const doc of modification.documents || []) { doc.id = generateUniqueDocId(existingDocuments); @@ -176,7 +178,7 @@ export async function updateWorkflowitem( return new VError(newEvent, "cannot update workflowitem"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "workflowitem.update"; if (!Workflowitem.permits(workflowitem, issuer, [intent])) { @@ -184,7 +186,7 @@ export async function updateWorkflowitem( } } - // Check that the new event is indeed valid: + logger.trace({ event: newEvent }, "Checking event validity"); const updatedWorkflowitemResult = WorkflowitemEventSourcing.newWorkflowitemFromEvent( ctx, workflowitem, @@ -200,7 +202,7 @@ export async function updateWorkflowitem( return { newEvents: [], workflowitem }; } - // Create notification events: + logger.trace("Creating notification events"); let notifications: Result.Type = []; if (workflowitem.assignee !== undefined) { const recipientsResult = await repository.getUsersForIdentity(workflowitem.assignee); diff --git a/api/src/service/domain/workflow/workflowitem_updated.ts b/api/src/service/domain/workflow/workflowitem_updated.ts index e63d536ea..5fd0c74b3 100644 --- a/api/src/service/domain/workflow/workflowitem_updated.ts +++ b/api/src/service/domain/workflow/workflowitem_updated.ts @@ -1,10 +1,10 @@ import Joi = require("joi"); +import logger from "lib/logger"; import { VError } from "verror"; - import * as Result from "../../../result"; import * as AdditionalData from "../additional_data"; -import { Identity } from "../organization/identity"; import { StoredDocument, storedDocumentSchema } from "../document/document"; +import { Identity } from "../organization/identity"; import { conversionRateSchema, moneyAmountSchema } from "./money"; import * as Project from "./project"; import * as Subproject from "./subproject"; @@ -70,6 +70,8 @@ export function createEvent( update: Modification, time: string = new Date().toISOString(), ): Result.Type { + logger.trace("Creating workflowitem_updated event"); + const event = { type: eventType, source, diff --git a/api/src/service/domain/workflow/workflowitems_reorder.ts b/api/src/service/domain/workflow/workflowitems_reorder.ts index 8a8e4dfb6..f2e2536e3 100644 --- a/api/src/service/domain/workflow/workflowitems_reorder.ts +++ b/api/src/service/domain/workflow/workflowitems_reorder.ts @@ -1,7 +1,7 @@ import isEqual = require("lodash.isequal"); import { VError } from "verror"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { InvalidCommand } from "../errors/invalid_command"; @@ -13,6 +13,7 @@ import * as Subproject from "./subproject"; import * as SubprojectEventSourcing from "./subproject_eventsourcing"; import * as WorkflowitemOrdering from "./workflowitem_ordering"; import * as WorkflowitemsReordered from "./workflowitems_reordered"; +import logger from "lib/logger"; interface Repository { getSubproject( @@ -40,6 +41,10 @@ export async function setWorkflowitemOrdering( return []; } + logger.trace( + { issuer, projectId, subprojectId, ordering }, + "Creating workflowitems_reordered event", + ); const reorderEvent = WorkflowitemsReordered.createEvent( ctx.source, issuer.id, @@ -51,7 +56,7 @@ export async function setWorkflowitemOrdering( return new VError(reorderEvent, "failed to create reorder event"); } - // Check authorization (if not root): + logger.trace({ issuer }, "Checking user authorization"); if (issuer.id !== "root") { const intent = "subproject.reorderWorkflowitems"; if (!Subproject.permits(subproject, issuer, [intent])) { @@ -59,7 +64,7 @@ export async function setWorkflowitemOrdering( } } - // Check that the new event is indeed valid: + logger.trace({ event: reorderEvent }, "Checking event validity"); const result = SubprojectEventSourcing.newSubprojectFromEvent(ctx, subproject, reorderEvent); if (Result.isErr(result)) { return new InvalidCommand(ctx, reorderEvent, [result]); @@ -68,7 +73,7 @@ export async function setWorkflowitemOrdering( // Only emit the event if it causes any changes: if (isEqual(subproject.workflowitemOrdering, result.workflowitemOrdering)) { return []; - } else { - return [reorderEvent]; } + + return [reorderEvent]; } diff --git a/api/src/service/domain/workflowitem_types/apply_workflowitem_type.ts b/api/src/service/domain/workflowitem_types/apply_workflowitem_type.ts index 7ab95dde4..ac5ac0663 100644 --- a/api/src/service/domain/workflowitem_types/apply_workflowitem_type.ts +++ b/api/src/service/domain/workflowitem_types/apply_workflowitem_type.ts @@ -1,4 +1,4 @@ -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { ServiceUser } from "../organization/service_user"; @@ -18,7 +18,12 @@ export const applyWorkflowitemType = ( workflowitemTypeEvents = []; break; case "restricted": - workflowitemTypeEvents = RestrictedWorkflowitem.createEvents(originEvent, ctx, publisher, workflowitem); + workflowitemTypeEvents = RestrictedWorkflowitem.createEvents( + originEvent, + ctx, + publisher, + workflowitem, + ); break; default: workflowitemTypeEvents = []; diff --git a/api/src/service/domain/workflowitem_types/restricted.ts b/api/src/service/domain/workflowitem_types/restricted.ts index 798698704..997f0be16 100644 --- a/api/src/service/domain/workflowitem_types/restricted.ts +++ b/api/src/service/domain/workflowitem_types/restricted.ts @@ -1,7 +1,7 @@ import { VError } from "verror"; import { workflowitemIntents } from "../../../authz/intents"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { BusinessEvent } from "../business_event"; import { NotAuthorized } from "../errors/not_authorized"; diff --git a/api/src/service/domain/workflowitem_types/workflowitem_type.spec.ts b/api/src/service/domain/workflowitem_types/workflowitem_type.spec.ts index 7a1b1dc46..cd9b06bd5 100644 --- a/api/src/service/domain/workflowitem_types/workflowitem_type.spec.ts +++ b/api/src/service/domain/workflowitem_types/workflowitem_type.spec.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { Ctx } from "../../../lib/ctx"; +import { Ctx } from "lib/ctx"; import * as Result from "../../../result"; import { NotAuthorized } from "../errors/not_authorized"; import { ServiceUser } from "../organization/service_user"; diff --git a/api/src/service/errors/authentication_failed.ts b/api/src/service/errors/authentication_failed.ts index 76aa581db..480e6c81e 100644 --- a/api/src/service/errors/authentication_failed.ts +++ b/api/src/service/errors/authentication_failed.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { Ctx } from "../../lib/ctx"; +import { Ctx } from "lib/ctx"; interface Info { ctx: Ctx; diff --git a/api/src/service/eventsourcing.ts b/api/src/service/eventsourcing.ts deleted file mode 100644 index b021bc81e..000000000 --- a/api/src/service/eventsourcing.ts +++ /dev/null @@ -1,374 +0,0 @@ -import Intent from "../authz/intents"; -import { People, Permissions } from "../authz/types"; -import deepcopy from "../lib/deepcopy"; -import { inheritDefinedProperties } from "../lib/inheritDefinedProperties"; -import logger from "../lib/logger"; -import { Event, throwUnsupportedEventVersion } from "./event"; -import { Item } from "./liststreamkeyitems"; -import * as Workflowitem from "./Workflowitem"; - -export interface Subproject { - id: string; - creationUnixTs: string; - status: "open" | "closed"; - displayName: string; - description: string; - assignee?: string; - currency: string; - projectedBudgets: ProjectedBudget[]; - permissions: Permissions; - log: HistoryEvent[]; - workflowitems: Map; -} - -interface ProjectedBudget { - organization: string; - value: string; - currencyCode: string; -} - -export interface Project { - id: string; - creationUnixTs: string; - status: "open" | "closed"; - displayName: string; - assignee?: string; - description: string; - projectedBudgets: ProjectedBudget[]; - thumbnail: string; - permissions: Permissions; - log: HistoryEvent[]; - subprojects: Map; -} - -export interface HistoryEvent { - key: string; // the resource ID (same for all events that relate to the same resource) - intent: Intent; - createdBy: string; - createdAt: string; - dataVersion: number; // integer - data: any; - snapshot: { - displayName: string; - }; -} - -const isProjectEvent = (item: Item) => item.keys.length === 1 && item.keys[0] === "self"; -const isSubprojectEvent = (item: Item) => item.keys.length === 2 && item.keys[0] === "subprojects"; -const isWorkflowitemEvent = (item: Item) => - item.keys.length === 2 && item.keys[0].endsWith("_workflows"); - -function applyProjectEvent(project: Project, item: Item): void { - const event = item.data.json as Event; - const hasProcessedEvent = - projectUpdated(event, project) || - assignedToProject(event, project) || - projectClosed(event, project) || - projectPermissionGranted(event, project) || - projectPermissionRevoked(event, project); - if (!hasProcessedEvent) { - logger.error(event, `Event ignored for key ${event.key}`); - } -} - -function applySubprojectEvent(project: Project, item: Item): void { - const event = item.data.json as Event; - let subproject = project.subprojects.get(event.key); - if (subproject === undefined) { - subproject = subprojectCreated(event); - if (subproject !== undefined) { - project.subprojects.set(event.key, subproject); - } else { - logger.error(event, `Subproject event ignored for project ${project.id}`); - } - } else { - const hasProcessedEvent = - subprojectUpdated(event, subproject) || - assignedToSubproject(event, subproject) || - subprojectClosed(event, subproject) || - subprojectPermissionGranted(event, subproject) || - subprojectPermissionRevoked(event, subproject); - if (!hasProcessedEvent) { - logger.error(event, `Subproject event ignored for project ${project.id}`); - } - } - - if (subproject !== undefined && isSubprojectEvent(item)) { - subproject.log.push({ - ...event, - snapshot: { displayName: deepcopy(subproject.displayName) }, - }); - } -} - -function applyWorkflowitemEvent(project: Project, subprojectId: string, item: Item): void { - const event = item.data.json as Event; - const subproject = project.subprojects.get(subprojectId); - if (subproject === undefined) { - logger.error(event, `Workflowitem event ignored for unknown subproject ${subprojectId}`); - return; - } - let workflowitem = subproject.workflowitems.get(event.key); - if (workflowitem === undefined) { - workflowitem = Workflowitem.handleCreate(event); - if (workflowitem !== undefined) { - subproject.workflowitems.set(event.key, workflowitem); - } else { - logger.error( - event, - `Workflowitem event ignored for subproject ${subproject.id} of project ${project.id}`, - ); - } - } else { - const hasProcessedEvent = - Workflowitem.applyUpdate(event, workflowitem) || - Workflowitem.applyAssign(event, workflowitem) || - Workflowitem.applyClose(event, workflowitem) || - Workflowitem.applyGrantPermission(event, workflowitem) || - Workflowitem.applyRevokePermission(event, workflowitem); - if (!hasProcessedEvent) { - logger.error( - event, - `Workflowitem event ignored for subproject ${subproject.id} of project ${project.id}`, - ); - } - } - - if (workflowitem !== undefined && isWorkflowitemEvent(item)) { - workflowitem.log.push({ - ...event, - snapshot: { - displayName: deepcopy(workflowitem.displayName), - amount: deepcopy(workflowitem.amount)!, - currency: deepcopy(workflowitem.currency)!, - amountType: deepcopy(workflowitem.amountType), - }, - }); - } -} - -export function applyStreamItems(streamItems: Item[], project?: Project): Project | undefined { - if (logger.levelVal >= logger.levels.values.trace) { - const action = project ? "Applying" : "Sourcing"; - const target = project ? ` to project ${project.id}` : ""; - logger.trace(`${action} ${streamItems.length} stream items${target}`); - } - for (const item of streamItems) { - const event = item.data.json as Event; - - if (event.intent === undefined) { - logger.debug({ event }, "cache1: ignoring event"); - continue; - } - - if (project === undefined) { - project = projectCreated(event); - } else { - if (isProjectEvent(item)) { - applyProjectEvent(project, item); - } else if (isSubprojectEvent(item)) { - applySubprojectEvent(project, item); - } else if (isWorkflowitemEvent(item)) { - const subprojectId = /^(.*)_workflows$/.exec(item.keys[0])![1]; - applyWorkflowitemEvent(project, subprojectId, item); - } else { - logger.error(event, `Event ignored for key ${JSON.stringify(item.keys)}`); - } - } - - if (project !== undefined && isProjectEvent(item)) { - project.log.push({ - ...event, - snapshot: { displayName: deepcopy(project.displayName) }, - }); - } - - logger.trace({ project, item }, "Applied stream item to project"); - } - return project; -} - -function projectCreated(event: Event): Project | undefined { - if (event.intent !== "global.createProject") return undefined; - switch (event.dataVersion) { - case 1: { - const { project, permissions } = event.data; - const values = { - ...deepcopy(project), - permissions: deepcopy(permissions), - log: [], - subprojects: new Map(), - }; - return values as Project; - } - } - throwUnsupportedEventVersion(event); -} - -function projectUpdated(event: Event, project: Project): true | undefined { - if (event.intent !== "project.update") return; - switch (event.dataVersion) { - case 1: { - inheritDefinedProperties(project, event.data); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function assignedToProject(event: Event, project: Project): true | undefined { - if (event.intent !== "project.assign") return; - switch (event.dataVersion) { - case 1: { - const { identity } = event.data; - project.assignee = identity; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function projectClosed(event: Event, project: Project): true | undefined { - if (event.intent !== "project.close") return; - switch (event.dataVersion) { - case 1: { - project.status = "closed"; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function projectPermissionGranted(event: Event, project: Project): true | undefined { - if (event.intent !== "project.intent.grantPermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - grantProjectPermission(project, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function grantProjectPermission(project: Project, identity: string, intent: Intent) { - const permissionsForIntent: People = project.permissions[intent] || []; - if (!permissionsForIntent.includes(identity)) { - permissionsForIntent.push(identity); - } - project.permissions[intent] = permissionsForIntent; -} - -function projectPermissionRevoked(event: Event, project: Project): true | undefined { - if (event.intent !== "project.intent.revokePermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - revokeProjectPermission(project, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function revokeProjectPermission(project: Project, identity: string, intent: Intent) { - const permissionsForIntent: People = project.permissions[intent] || []; - const userIndex = permissionsForIntent.indexOf(identity); - if (userIndex !== -1) { - // Remove the user from the array: - permissionsForIntent.splice(userIndex, 1); - project.permissions[intent] = permissionsForIntent; - } -} - -function subprojectCreated(event: Event): Subproject | undefined { - if (event.intent !== "project.createSubproject") return undefined; - switch (event.dataVersion) { - case 1: { - const { subproject, permissions } = event.data; - return { - ...deepcopy(subproject), - permissions: deepcopy(permissions), - log: [], - workflowitems: new Map(), - }; - } - } - throwUnsupportedEventVersion(event); -} - -function subprojectUpdated(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.update") return; - switch (event.dataVersion) { - case 1: { - inheritDefinedProperties(subproject, event.data); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function assignedToSubproject(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.assign") return; - switch (event.dataVersion) { - case 1: { - const { identity } = event.data; - subproject.assignee = identity; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function subprojectClosed(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.close") return; - switch (event.dataVersion) { - case 1: { - subproject.status = "closed"; - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function subprojectPermissionGranted(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.intent.grantPermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - grantSubprojectPermission(subproject, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function subprojectPermissionRevoked(event: Event, subproject: Subproject): true | undefined { - if (event.intent !== "subproject.intent.revokePermission") return; - switch (event.dataVersion) { - case 1: { - const { identity, intent } = event.data; - revokeSubprojectPermission(subproject, identity, intent); - return true; - } - } - throwUnsupportedEventVersion(event); -} - -function grantSubprojectPermission(subproject: Subproject, identity: string, intent: Intent) { - const permissionsForIntent: People = subproject.permissions[intent] || []; - if (!permissionsForIntent.includes(identity)) { - permissionsForIntent.push(identity); - } - subproject.permissions[intent] = permissionsForIntent; -} - -function revokeSubprojectPermission(subproject: Subproject, identity: string, intent: Intent) { - const permissionsForIntent: People = subproject.permissions[intent] || []; - const userIndex = permissionsForIntent.indexOf(identity); - if (userIndex !== -1) { - // Remove the user from the array: - permissionsForIntent.splice(userIndex, 1); - subproject.permissions[intent] = permissionsForIntent; - } -} diff --git a/api/src/service/global_permission_grant.ts b/api/src/service/global_permission_grant.ts index 2d45aa614..bb672a695 100644 --- a/api/src/service/global_permission_grant.ts +++ b/api/src/service/global_permission_grant.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -19,6 +20,7 @@ export async function grantGlobalPermission( grantee: Identity, permission: Intent, ): Promise> { + logger.debug({ permission, grantee }, "Grantig global permission"); const result = await GlobalPermissionsGrant.grantGlobalPermission( ctx, serviceUser, diff --git a/api/src/service/global_permission_revoke.ts b/api/src/service/global_permission_revoke.ts index da3b3d3c4..7167676f6 100644 --- a/api/src/service/global_permission_revoke.ts +++ b/api/src/service/global_permission_revoke.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -19,6 +20,7 @@ export async function revokeGlobalPermission( revokee: Identity, permission: Intent, ): Promise> { + logger.debug({ permission, revokee }, "Revoking global permission"); const result = await GlobalPermissionsRevoke.revokeGlobalPermission( ctx, serviceUser, diff --git a/api/src/service/global_permissions_get.ts b/api/src/service/global_permissions_get.ts index 75c4b7ca2..92003db5e 100644 --- a/api/src/service/global_permissions_get.ts +++ b/api/src/service/global_permissions_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; import * as Cache from "./cache2"; @@ -11,6 +12,7 @@ export async function getGlobalPermissions( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Getting Global Permissions"); return Cache.withCache( conn, ctx, diff --git a/api/src/service/grantpermissiontoaddress.ts b/api/src/service/grantpermissiontoaddress.ts index 3a11cd59a..6a97d6341 100644 --- a/api/src/service/grantpermissiontoaddress.ts +++ b/api/src/service/grantpermissiontoaddress.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { MultichainClient } from "./Client.h"; export function grantpermissiontoaddress( @@ -5,5 +6,6 @@ export function grantpermissiontoaddress( address: string, permissions: string[], ): Promise { + logger.debug({ address, permissions }, "Granting multichain permissions to address"); return multichain.getRpcClient().invoke("grant", address, permissions.join(",")); } diff --git a/api/src/service/group_create.ts b/api/src/service/group_create.ts index 6e0deb99a..4c3c1a984 100644 --- a/api/src/service/group_create.ts +++ b/api/src/service/group_create.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -22,11 +23,14 @@ export async function createGroup( serviceUser: ServiceUser, requestData: GroupCreate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Creating Group"); + const groupCreateResult = await GroupCreate.createGroup(ctx, serviceUser, requestData, { getGlobalPermissions: async () => getGlobalPermissions(conn, ctx, serviceUser), groupExists: async (groupId) => groupExists(conn, ctx, serviceUser, groupId), userExists: async (groupId) => userExists(conn, ctx, serviceUser, groupId), }); + if (Result.isErr(groupCreateResult)) return new VError(groupCreateResult, "create group failed"); const newEvents = groupCreateResult; diff --git a/api/src/service/group_member_add.ts b/api/src/service/group_member_add.ts index 8d86d213c..678fbf6ec 100644 --- a/api/src/service/group_member_add.ts +++ b/api/src/service/group_member_add.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -15,6 +16,8 @@ export async function addMember( groupId: Group.Id, newMember: Group.Member, ): Promise> { + logger.debug({ newMember }, `Adding member to group "${groupId}"`); + const memberAddResult = await Cache.withCache(conn, ctx, (cache) => GroupMemberAdd.addMember(ctx, serviceUser, groupId, newMember, { getGroupEvents: async () => { diff --git a/api/src/service/group_member_remove.ts b/api/src/service/group_member_remove.ts index 4a5fd03ff..e224f69d8 100644 --- a/api/src/service/group_member_remove.ts +++ b/api/src/service/group_member_remove.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -13,10 +14,12 @@ export async function removeMember( ctx: Ctx, serviceUser: ServiceUser, groupId: Group.Id, - newMember: Group.Member, + userId: Group.Member, ): Promise> { + logger.debug({ userId }, "Removing user from group"); + const memberRemoveResult = await Cache.withCache(conn, ctx, (cache) => - GroupMemberRemove.removeMember(ctx, serviceUser, groupId, newMember, { + GroupMemberRemove.removeMember(ctx, serviceUser, groupId, userId, { getGroupEvents: async () => { return cache.getGroupEvents(); }, diff --git a/api/src/service/group_permissions_list.ts b/api/src/service/group_permissions_list.ts index 23923c109..349728579 100644 --- a/api/src/service/group_permissions_list.ts +++ b/api/src/service/group_permissions_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; @@ -15,6 +16,8 @@ export async function getGroupPermissions( serviceUser: ServiceUser, groupId: Group.Id, ): Promise> { + logger.debug({ groupId }, "Get group permissions"); + const groupResult = await Cache.withCache(conn, ctx, async (cache) => GroupGet.getOneGroup(ctx, serviceUser, groupId, { getGroupEvents: async () => cache.getGroupEvents(groupId), diff --git a/api/src/service/group_query.ts b/api/src/service/group_query.ts index ece5b6811..a35ace784 100644 --- a/api/src/service/group_query.ts +++ b/api/src/service/group_query.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; @@ -19,6 +20,8 @@ export async function getGroups( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Getting all groups"); + try { const groups = await Cache.withCache(conn, ctx, (cache) => GroupGet.getAllGroups(ctx, serviceUser, { @@ -39,13 +42,19 @@ export async function getGroup( serviceUser: ServiceUser, groupId: Group.Id, ): Promise> { + logger.debug(`Getting group with id "${groupId}"`); + const groupsResult = await getGroups(conn, ctx, serviceUser); + if (Result.isErr(groupsResult)) return groupsResult; + const groups = groupsResult; const group = groups.find((x) => x.id === groupId); + if (group === undefined) { return new NotFound(ctx, "group", groupId); } + return group; } @@ -55,9 +64,14 @@ export async function getGroupsForUser( serviceUser: ServiceUser, targetUserId: Identity, ): Promise> { + logger.debug({ user: targetUserId }, "Get groups for user"); + const groupsResult = await getGroups(conn, ctx, serviceUser); + if (Result.isErr(groupsResult)) return groupsResult; + const groups = groupsResult; + return groups.filter((group) => group.members.includes(targetUserId)); } @@ -67,8 +81,11 @@ export async function groupExists( serviceUser: ServiceUser, groupId: Group.Id, ): Promise> { + logger.debug({ groupId }, "Checking if group exists"); const groupsResult = await getGroups(conn, ctx, serviceUser); + if (Result.isErr(groupsResult)) return groupsResult; + const groups = groupsResult; return groups.find((x) => x.id === groupId) !== undefined; } @@ -87,17 +104,21 @@ export async function resolveUsers( getUserFn: typeof getUser = getUser, groupSet: Set = new Set(), ): Promise> { + logger.debug({ identity }, "Getting all users of group"); const groupResult = await getGroupFn(conn, ctx, serviceUser, identity); // if assignee is not a group, it is probably a user if (Result.isErr(groupResult)) { //check if assignee does exist + logger.debug({ identity }, "Identity is not a group, checking if it is a user"); + const userResult = await getUserFn(conn, ctx, serviceUser, identity); if (Result.isErr(userResult)) { return new NotFound(ctx, "user", identity); - } else { - return [identity]; } + + logger.debug({ user: userResult }, "Identity is a user"); + return [identity]; } const users: UserRecord.Id[] = []; diff --git a/api/src/service/groups.ts b/api/src/service/groups.ts index 9d8dd3b63..50d99ec0f 100644 --- a/api/src/service/groups.ts +++ b/api/src/service/groups.ts @@ -6,12 +6,10 @@ import { MultichainClient } from "./Client.h"; import { ConnToken } from "./conn"; import * as Liststreamkeyitems from "./liststreamkeyitems"; -// -// public -// - export function getUsers(conn: ConnToken, groupId: string): Promise { if (isEmpty(groupId)) return Promise.resolve([]); + + logger.debug({ groupId }, "Getting users of group"); return getGroup(conn.multichainClient, groupId) .then((group) => group.users) .catch((_) => []); @@ -33,10 +31,6 @@ export async function removeUser( throw Error("not implemented"); } -// -// private -// - interface Group { groupId: string; displayName: string; @@ -44,13 +38,19 @@ interface Group { } async function getGroup(multichain: MultichainClient, groupId: string): Promise { + logger.debug({ groupId }, "Getting group from multichain"); + await ensureStreamExists(multichain); + const groupEvents = await multichain.v2_readStreamItems("groups", groupId); const resourceMap = mapItems(groupEvents); + return resourceMap.values().next().value; } function ensureStreamExists(multichain: MultichainClient): Promise { + logger.trace("Checking if group stream exists on multichain"); + return multichain.getOrCreateStream({ kind: "groups", name: "groups", @@ -63,14 +63,20 @@ function asMapKey(keys: string[]): string { function mapItems(streamItems: Liststreamkeyitems.Item[]): Map { const resourceMap = new Map(); + for (const item of streamItems) { const event = item.data.json as Multichain.Event; let resource = resourceMap.get(asMapKey(item.keys)); + + logger.trace({ event, resource }, "Mapping and applying events for user in group"); + if (resource === undefined) { const result = applyCreate(event); + if (result === undefined) { throw Error(`Failed to initialize resource: ${JSON.stringify(event)}.`); } + resource = result.resource; resourceMap.set(asMapKey(item.keys), resource); } else { @@ -81,6 +87,7 @@ function mapItems(streamItems: Liststreamkeyitems.Item[]): Map { } } } + return resourceMap; } @@ -88,6 +95,7 @@ function applyCreate(event: Multichain.Event): { resource: Group } | undefined { if (event.intent !== "global.createGroup") { return undefined; } + switch (event.dataVersion) { case 1: { const { group } = event.data; @@ -98,6 +106,7 @@ function applyCreate(event: Multichain.Event): { resource: Group } | undefined { }; } } + Multichain.throwUnsupportedEventVersion(event); } @@ -105,6 +114,7 @@ function applyAddUser(event: Multichain.Event, resource: Group): true | undefine if (event.intent !== "group.addUser") { return; } + switch (event.dataVersion) { case 1: { logger.trace( @@ -114,6 +124,7 @@ function applyAddUser(event: Multichain.Event, resource: Group): true | undefine return true; } } + Multichain.throwUnsupportedEventVersion(event); } @@ -121,6 +132,7 @@ function applyRemoveUser(event: Multichain.Event, resource: Group): true | undef if (event.intent !== "group.removeUser") { return; } + switch (event.dataVersion) { case 1: { const index = resource.users.indexOf(event.data.userId); @@ -133,5 +145,6 @@ function applyRemoveUser(event: Multichain.Event, resource: Group): true | undef return true; } } + Multichain.throwUnsupportedEventVersion(event); } diff --git a/api/src/service/hexconverter.ts b/api/src/service/hexconverter.ts index 86e76b08c..26cd12481 100644 --- a/api/src/service/hexconverter.ts +++ b/api/src/service/hexconverter.ts @@ -16,10 +16,11 @@ export const stringToHex = (text = "") => { c = text.charCodeAt(i); str += d2h(c); } + return str; }; -export const hexToString = hex => { +export const hexToString = (hex) => { const hexArray = hex.match(/.{2}/g); let str = ""; let i = 0; @@ -34,19 +35,19 @@ export const hexToString = hex => { return str; }; -export const objectToHex = object => { +export const objectToHex = (object) => { const cleansedString = removeControlCharacter(JSON.stringify(object)); return stringToHex(cleansedString); }; -const removeControlCharacter = json => json.replace(/[\x00-\x1F\x7F-\x9F]/g, ""); +const removeControlCharacter = (json) => json.replace(/[\x00-\x1F\x7F-\x9F]/g, ""); // const removeInvisibleChars = (x: string): string => x.replace(/[^\P{C}]/gu, ""); /* * Parse hex string, throws if not parseable. */ -export const hexToObject = hex => { +export const hexToObject = (hex) => { const cleansedString = removeControlCharacter(hexToString(hex)); return JSON.parse(cleansedString); }; diff --git a/api/src/service/importprivkey.ts b/api/src/service/importprivkey.ts index c18443a77..fa363374f 100644 --- a/api/src/service/importprivkey.ts +++ b/api/src/service/importprivkey.ts @@ -7,5 +7,6 @@ export function importprivkey( ): Promise { const label = userId; const doRescan = false; + return multichain.getRpcClient().invoke("importprivkey", privkey, label, doRescan); } diff --git a/api/src/service/index.ts b/api/src/service/index.ts index dd4164ddb..b9e361a75 100644 --- a/api/src/service/index.ts +++ b/api/src/service/index.ts @@ -1,26 +1,18 @@ -import uuid = require("uuid"); - import Intent from "../authz/intents"; import { People, Permissions } from "../authz/types"; -import deepcopy from "../lib/deepcopy"; import logger from "../lib/logger"; import * as Cache from "./cache"; import * as Cache2 from "./cache2"; -import { asMapKey } from "./Client"; import { RpcMultichainClient } from "./Client.h"; import { ConnToken } from "./conn"; import { Event, throwUnsupportedEventVersion } from "./event"; import { Issuer } from "./issuer"; import { ConnectionSettings } from "./RpcClient.h"; -import * as MultichainWorkflowitem from "./Workflowitem"; +import VError from "verror"; -export * from "./cache"; export * from "./event"; -export * from "./Workflowitem"; -export * from "./SubprojectEvents"; export * from "./issuer"; export * from "./ProjectEvents"; -export * from "./SubprojectEvents"; export { ConnToken } from "./conn"; const workflowitemsGroupKey = (subprojectId) => `${subprojectId}_workflows`; @@ -29,13 +21,28 @@ const globalSelfKey = "self"; type ResourceType = "project" | "subproject" | "workflowitem"; -interface NotificationResourceDescription { +interface Update { + displayName?: string; + amount?: string; + currency?: string; + amountType?: "N/A" | "disbursed" | "allocated"; + description?: string; + documents?: Document[]; + exchangeRate?: string; + billingDate?: string; + dueDate?: string; +} +interface Document { id: string; - type: ResourceType; + hash: string; + fileName?: string; } export function init(rpcSettings: ConnectionSettings): ConnToken { + logger.debug({ rpcSettings }, "Initialising RpcMultichainClient with rpcSettings"); + const multichainClient = new RpcMultichainClient(rpcSettings); + return { multichainClient, cache: Cache.initCache(), @@ -43,26 +50,6 @@ export function init(rpcSettings: ConnectionSettings): ConnToken { }; } -export async function getGlobalPermissionList(conn: ConnToken): Promise { - try { - const streamItems = await conn.multichainClient.v2_readStreamItems("global", globalSelfKey, 1); - if (streamItems.length < 1) { - return {}; - } - const event: Event = streamItems[0].data.json; - return event.data.permissions; - } catch (err) { - if (err.kind === "NotFound") { - // Happens at startup, no need to worry... - logger.debug("Global permissions not found. Happens at startup."); - return {}; - } else { - logger.error({ err }, "Error while retrieving global permissions"); - throw err; - } - } -} - export async function grantGlobalPermission( conn: ConnToken, issuer: Issuer, @@ -71,6 +58,7 @@ export async function grantGlobalPermission( ): Promise { const permissions = await getGlobalPermissionList(conn); const permissionsForIntent: People = permissions[intent] || []; + permissionsForIntent.push(grantee); permissions[intent] = permissionsForIntent; @@ -116,10 +104,13 @@ export async function revokeGlobalPermission( recipient: string, intent: Intent, ): Promise { + logger.debug({ intent, recipient }, "Revoking global permission"); const permissions = await getGlobalPermissionList(conn); + if (permissions === {}) { - return; + throw new VError("No global permissions found, escaping"); } + const permissionsForIntent: People = permissions[intent] || []; const userIndex = permissionsForIntent.indexOf(recipient); permissionsForIntent.splice(userIndex, 1); @@ -159,156 +150,26 @@ export async function revokeGlobalPermission( }); } -export async function issueNotification( - conn: ConnToken, - issuer: Issuer, - message: Event, - recipient: string, - resources: NotificationResourceDescription[], -): Promise { - const notificationId = uuid(); - // TODO message.key is working for projects - // TODO but we need to access projectId subprojectid and workflowitemid and build data.resources - // const projectId = message.key; - // const resources: NotificationResourceDescription[] = [ - // { - // id: projectId, - // type: notificationTypeFromIntent(message.intent), - // }, - // ]; - const intent = "notification.create"; - const event: Event = { - key: recipient, - intent, - createdBy: issuer.name, - createdAt: new Date().toISOString(), - dataVersion: 1, - data: { - notificationId, - resources, - isRead: false, - originalEvent: message, - }, - }; - const streamName = "notifications"; - const publishEvent = () => { - logger.debug(`Publishing ${intent} to ${streamName}/${recipient}`); - return conn.multichainClient.getRpcClient().invokePublish(streamName, recipient, { - json: event, - }); - }; - return publishEvent().catch((err) => { - if (err.code === -708) { - logger.debug( - `The stream ${streamName} does not exist yet. Creating the stream and trying again.`, - ); - // The stream does not exist yet. Create the stream and try again: - return conn.multichainClient - .getOrCreateStream({ kind: "notifications", name: streamName }) - .then(() => publishEvent()); - } else { - logger.error({ err }, `Publishing ${intent} failed.`); - throw err; - } - }); -} - -function notificationTypeFromIntent(intent: Intent): ResourceType { - if (intent.startsWith("project.") || intent === "global.createProject") { - return "project"; - } else if (intent.startsWith("subproject.") || intent === "project.createSubproject") { - return "subproject"; - } else if (intent.startsWith("workflowitem.") || intent === "subproject.createWorkflowitem") { - return "workflowitem"; - } else { - throw Error(`Unknown ResourceType for intent ${intent}`); - } -} - -export function generateResources( - projectId: string, - subprojectId?: string, - workflowitemId?: string, -): NotificationResourceDescription[] { - const notificationResource: NotificationResourceDescription[] = []; - if (!projectId) { - throw { kind: "PreconditionError", message: "No project ID provided" }; - } - notificationResource.push({ - id: projectId, - type: "project", - }); - if (subprojectId) { - notificationResource.push({ - id: subprojectId, - type: "subproject", - }); - } - if (workflowitemId) { - notificationResource.push({ - id: workflowitemId, - type: "workflowitem", - }); - } - - return notificationResource; -} - -export async function getWorkflowitemList( - conn: ConnToken, - projectId: string, - subprojectId: string, -): Promise { - const queryKey = workflowitemsGroupKey(subprojectId); - - const streamItems = await conn.multichainClient.v2_readStreamItems(projectId, queryKey); - const workflowitemsMap = new Map(); - - for (const item of streamItems) { - const event = item.data.json as Event; - - // Events look differently for different intents! - let workflowitem = workflowitemsMap.get(asMapKey(item)); - - if (workflowitem === undefined) { - // If we didn't encounter the workflowitem while looping we just need to create - // a workflowitem with no data in it - workflowitem = MultichainWorkflowitem.handleCreate(event); +async function getGlobalPermissionList(conn: ConnToken): Promise { + try { + const streamItems = await conn.multichainClient.v2_readStreamItems("global", globalSelfKey, 1); - if (workflowitem === undefined) { - throw Error(`Failed to initialize resource: ${JSON.stringify(event)}.`); - } - } else { - // We've already encountered this workflowitem, so we can apply operations on it. - const hasProcessedEvent = - MultichainWorkflowitem.applyUpdate(event, workflowitem) || - MultichainWorkflowitem.applyAssign(event, workflowitem) || - MultichainWorkflowitem.applyClose(event, workflowitem) || - MultichainWorkflowitem.applyGrantPermission(event, workflowitem) || - MultichainWorkflowitem.applyRevokePermission(event, workflowitem); - if (!hasProcessedEvent) { - const message = "Unexpected event occured"; - throw Error(`${message}: ${JSON.stringify(event)}.`); - } + if (streamItems.length < 1) { + return {}; } - if (workflowitem !== undefined) { - // Save all events to the log for now; we'll filter them once we - // know the final workflowitem permissions. - workflowitem.log.push({ - ...event, - snapshot: { - displayName: deepcopy(workflowitem.displayName), - amount: deepcopy(workflowitem.amount)!, - currency: deepcopy(workflowitem.currency)!, - amountType: deepcopy(workflowitem.amountType), - }, - }); - workflowitemsMap.set(asMapKey(item), workflowitem); + const event: Event = streamItems[0].data.json; + return event.data.permissions; + } catch (err) { + if (err.kind === "NotFound") { + // Happens at startup, no need to worry... + logger.debug("Global permissions not found. Happens at startup."); + return {}; } - } - return [...workflowitemsMap.values()]; + logger.error({ err }, "Error while retrieving global permissions"); + throw err; + } } export async function getWorkflowitemOrdering( @@ -316,6 +177,7 @@ export async function getWorkflowitemOrdering( projectId: string, subprojectId: string, ): Promise { + logger.debug({ projectId, subprojectId }, "Getting order of workflowitems"); // Currently, the workflowitem ordering is stored in full; therefore, we only // need to retrieve the latest item(see`publishWorkflowitemOrdering`). const expectedDataVersion = 1; @@ -328,15 +190,18 @@ export async function getWorkflowitemOrdering( else throw { kind: "NotFound", what: workflowitemOrderingKey(subprojectId) }; }) .catch((err) => { + logger.error({ err }, "Error while getting order of workflowitems"); + if (err.kind === "NotFound") { return [{ data: { json: { dataVersion: 1, data: [] } } }]; - } else { - throw err; } + + throw err; }); const item = streamItems[0]; const event = item.data.json as Event; + if (event.dataVersion !== expectedDataVersion) { throwUnsupportedEventVersion(event); } @@ -380,7 +245,7 @@ export function updateWorkflowitem( projectId: string, subprojectId: string, workflowitemId: string, - data: MultichainWorkflowitem.Update, + data: Update, ): Promise { const intent: Intent = "workflowitem.update"; diff --git a/api/src/service/notification_list.ts b/api/src/service/notification_list.ts index 2e0b32100..a8ce71dbf 100644 --- a/api/src/service/notification_list.ts +++ b/api/src/service/notification_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; import * as Cache from "./cache2"; @@ -12,6 +13,8 @@ export async function getNotificationsForUser( ctx: Ctx, user: ServiceUser, ): Promise> { + logger.debug({ user }, "Getting notifications for user"); + return await Cache.withCache(conn, ctx, (cache) => NotificationList.getUserNotifications(ctx, user, { getUserNotificationEvents: async (userId: UserRecord.Id) => { diff --git a/api/src/service/notification_mark_read.ts b/api/src/service/notification_mark_read.ts index f6b742c55..3dd32334f 100644 --- a/api/src/service/notification_mark_read.ts +++ b/api/src/service/notification_mark_read.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -17,7 +18,9 @@ export async function markRead( notificationIds: Notification.Id[], ): Promise> { try { + logger.debug({ serviceUser }, "ServiceUser marking notifications as read"); let newEvents: BusinessEvent[] = []; + for (const id of notificationIds) { const newEventResult = await Cache.withCache(conn, ctx, (cache) => NotificationMarkRead.markRead(ctx, serviceUser, id, { @@ -26,9 +29,11 @@ export async function markRead( }, }), ); + if (Result.isErr(newEventResult)) { return new VError(newEventResult, `failed to mark notification ${id} as read`); } + newEvents = newEvents.concat(newEventResult); } @@ -36,6 +41,7 @@ export async function markRead( await store(conn, ctx, newEvent, serviceUser.address); } } catch (error) { + logger.error({ err: error }, "Error while marking notifications as read"); return error; } } diff --git a/api/src/service/project_assign.ts b/api/src/service/project_assign.ts index c52c175b2..20952efc2 100644 --- a/api/src/service/project_assign.ts +++ b/api/src/service/project_assign.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -17,6 +18,8 @@ export async function assignProject( projectId: Project.Id, assignee: Identity, ): Promise> { + logger.debug({ projectId, assignee }, "Assigning project to user"); + const assignProjectresult = await Cache.withCache(conn, ctx, async (cache) => ProjectAssign.assignProject(ctx, serviceUser, projectId, assignee, { getProject: async () => { @@ -31,6 +34,7 @@ export async function assignProject( if (Result.isErr(assignProjectresult)) { return new VError(assignProjectresult, `assign ${assignee} to project failed`); } + const { newEvents } = assignProjectresult; for (const event of newEvents) { diff --git a/api/src/service/project_close.ts b/api/src/service/project_close.ts index 1b613cfa1..94431bbaa 100644 --- a/api/src/service/project_close.ts +++ b/api/src/service/project_close.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -15,6 +16,8 @@ export async function closeProject( serviceUser: ServiceUser, projectId: Project.Id, ): Promise> { + logger.debug({ projectId }, "Closing project"); + const closeProjectResult = await Cache.withCache(conn, ctx, async (cache) => ProjectClose.closeProject(ctx, serviceUser, projectId, { getProject: async () => { diff --git a/api/src/service/project_create.ts b/api/src/service/project_create.ts index b233bb1e4..b98f1f16c 100644 --- a/api/src/service/project_create.ts +++ b/api/src/service/project_create.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -17,6 +18,8 @@ export async function createProject( serviceUser: ServiceUser, requestData: ProjectCreate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Creating project"); + const creationEventResult = await Cache.withCache(conn, ctx, async (cache) => ProjectCreate.createProject(ctx, serviceUser, requestData, { getGlobalPermissions: async () => getGlobalPermissions(conn, ctx, serviceUser), diff --git a/api/src/service/project_get.ts b/api/src/service/project_get.ts index f391fd482..49d0392f8 100644 --- a/api/src/service/project_get.ts +++ b/api/src/service/project_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; @@ -14,15 +15,18 @@ export async function getProject( serviceUser: ServiceUser, projectId: Project.Id, ): Promise> { - const projectResult = await Cache.withCache(conn, ctx, async cache => + logger.debug({ projectId }, "Getting Project"); + + const projectResult = await Cache.withCache(conn, ctx, async (cache) => ProjectGet.getProject(ctx, serviceUser, projectId, { - getProject: async pId => { + getProject: async (pId) => { return cache.getProject(pId); }, }), ); + return Result.mapErr( projectResult, - err => new VError(err, `could not fetch project ${projectId}`), + (err) => new VError(err, `could not fetch project ${projectId}`), ); } diff --git a/api/src/service/project_history_get.ts b/api/src/service/project_history_get.ts index a3691297d..f815a7667 100644 --- a/api/src/service/project_history_get.ts +++ b/api/src/service/project_history_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import VError = require("verror"); import { Ctx } from "../lib/ctx"; @@ -17,6 +18,8 @@ export async function getProjectHistory( projectId: Project.Id, filter?: History.Filter, ): Promise> { + logger.debug({ projectId, filter }, "Getting history of project"); + const projectHistoryResult = await Cache.withCache(conn, ctx, async (cache) => ProjectHistory.getHistory( ctx, @@ -30,6 +33,7 @@ export async function getProjectHistory( filter, ), ); + return Result.mapErr( projectHistoryResult, (err) => new VError(err, `could not get history of project with id ${projectId}`), diff --git a/api/src/service/project_list.ts b/api/src/service/project_list.ts index 9e792ceff..fffa77ddc 100644 --- a/api/src/service/project_list.ts +++ b/api/src/service/project_list.ts @@ -6,12 +6,15 @@ import * as Project from "./domain/workflow/project"; import * as ProjectList from "./domain/workflow/project_list"; import { VError } from "verror"; import * as Result from "../result"; +import logger from "lib/logger"; export async function listProjects( conn: ConnToken, ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Listing all projects"); + const visibleProjectsResult = await Cache.withCache(conn, ctx, async (cache) => ProjectList.getAllVisible(ctx, serviceUser, { getAllProjects: async () => { @@ -19,5 +22,6 @@ export async function listProjects( }, }), ); + return Result.mapErr(visibleProjectsResult, (err) => new VError(err, "list projects failed")); } diff --git a/api/src/service/project_permission_grant.ts b/api/src/service/project_permission_grant.ts index ab3bbe818..2aec01d2c 100644 --- a/api/src/service/project_permission_grant.ts +++ b/api/src/service/project_permission_grant.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -20,6 +21,8 @@ export async function grantProjectPermission( grantee: Identity, intent: Intent, ): Promise> { + logger.debug({ projectId, grantee, intent }, "Granting project permission"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => ProjectPermissionGrant.grantProjectPermission(ctx, serviceUser, projectId, grantee, intent, { getProject: async (id) => { @@ -27,9 +30,11 @@ export async function grantProjectPermission( }, }), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "grant project permission failed"); } + const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/project_permission_revoke.ts b/api/src/service/project_permission_revoke.ts index 42c9780f9..796292f48 100644 --- a/api/src/service/project_permission_revoke.ts +++ b/api/src/service/project_permission_revoke.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -20,6 +21,8 @@ export async function revokeProjectPermission( revokee: Identity, intent: Intent, ): Promise> { + logger.debug({ projectId, revokee, intent }, "Revoking project permission"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => ProjectPermissionRevoke.revokeProjectPermission(ctx, serviceUser, projectId, revokee, intent, { getProject: async (id) => { @@ -31,7 +34,9 @@ export async function revokeProjectPermission( if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "revoke project permission failed"); } + const newEvents = newEventsResult; + for (const event of newEvents) { await store(conn, ctx, event, serviceUser.address); } diff --git a/api/src/service/project_permissions_list.ts b/api/src/service/project_permissions_list.ts index da794c9b9..ee553c594 100644 --- a/api/src/service/project_permissions_list.ts +++ b/api/src/service/project_permissions_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; import * as Cache from "./cache2"; @@ -13,13 +14,16 @@ export async function getProjectPermissions( serviceUser: ServiceUser, projectId: Project.Id, ): Promise> { - const projectPermissionsResult = await Cache.withCache(conn, ctx, async cache => + logger.debug({ projectId }, "Get project permissions"); + + const projectPermissionsResult = await Cache.withCache(conn, ctx, async (cache) => ProjectPermissionsList.getProjectPermissions(ctx, serviceUser, projectId, { - getProject: async pId => { + getProject: async (pId) => { return cache.getProject(pId); }, }), ); + if (Result.isErr(projectPermissionsResult)) { projectPermissionsResult.message = `could not fetch project permissions: ${projectPermissionsResult.message}`; return projectPermissionsResult; diff --git a/api/src/service/project_projected_budget_delete.ts b/api/src/service/project_projected_budget_delete.ts index 6b239faa0..67e6f2fe4 100644 --- a/api/src/service/project_projected_budget_delete.ts +++ b/api/src/service/project_projected_budget_delete.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -19,6 +20,8 @@ export async function deleteProjectedBudget( organization: string, currencyCode: CurrencyCode, ): Promise> { + logger.debug({ projectId, organization, currencyCode }, "Deleting project budget"); + const deleteProjectedBudgetResult = await Cache.withCache(conn, ctx, async (cache) => ProjectProjectedBudgetDelete.deleteProjectedBudget( ctx, @@ -36,9 +39,11 @@ export async function deleteProjectedBudget( }, ), ); + if (Result.isErr(deleteProjectedBudgetResult)) { return new VError(deleteProjectedBudgetResult, "delete projected budget failed"); } + const { newEvents, projectedBudgets } = deleteProjectedBudgetResult; for (const event of newEvents) { diff --git a/api/src/service/project_projected_budget_update.ts b/api/src/service/project_projected_budget_update.ts index 54bcb2145..b25abc7fc 100644 --- a/api/src/service/project_projected_budget_update.ts +++ b/api/src/service/project_projected_budget_update.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -20,6 +21,8 @@ export async function updateProjectedBudget( value: MoneyAmount, currencyCode: CurrencyCode, ): Promise> { + logger.debug({ projectId, organization, value, currencyCode }, "Updating project budget"); + const updateProjectedBudgetResult = await Cache.withCache(conn, ctx, async (cache) => ProjectProjectedBudgetUpdate.updateProjectedBudget( ctx, @@ -38,9 +41,11 @@ export async function updateProjectedBudget( }, ), ); + if (Result.isErr(updateProjectedBudgetResult)) { return new VError(updateProjectedBudgetResult, "delete projected budget failed"); } + const { newEvents, projectedBudgets } = updateProjectedBudgetResult; for (const event of newEvents) { diff --git a/api/src/service/project_update.ts b/api/src/service/project_update.ts index 1d7d8084c..9477b9096 100644 --- a/api/src/service/project_update.ts +++ b/api/src/service/project_update.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -16,6 +17,8 @@ export async function updateProject( projectId: Project.Id, requestData: ProjectUpdate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Updating project"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => ProjectUpdate.updateProject(ctx, serviceUser, projectId, requestData, { getProject: async (pId) => { @@ -26,9 +29,11 @@ export async function updateProject( }, }), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "grant project permission failed"); } + const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/provisioning_end.ts b/api/src/service/provisioning_end.ts index 8bfda61b6..17f2a6483 100644 --- a/api/src/service/provisioning_end.ts +++ b/api/src/service/provisioning_end.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -12,6 +13,8 @@ export async function setProvisioningEndFlag( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Setting flag to signal end of provisioning"); + const provisioningEndEventResult = await Cache.withCache(conn, ctx, async (cache) => ProvisioningEnd.setProvisioningEndFlag(ctx, serviceUser), ); diff --git a/api/src/service/provisioning_get.ts b/api/src/service/provisioning_get.ts index de589b872..5a98946f0 100644 --- a/api/src/service/provisioning_get.ts +++ b/api/src/service/provisioning_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -12,6 +13,8 @@ export async function getProvisionStatus( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Getting provisioning status"); + const provisionedResult = await Cache.withCache(conn, ctx, async (cache) => ProvisionedGet.getProvisionStatus(ctx, serviceUser, { getSystemInformationEvents: async () => { @@ -19,6 +22,7 @@ export async function getProvisionStatus( }, }), ); + return Result.mapErr( provisionedResult, (err) => new VError(err, "get provisioned status failed"), diff --git a/api/src/service/provisioning_start.ts b/api/src/service/provisioning_start.ts index 735bd360e..8a40f4594 100644 --- a/api/src/service/provisioning_start.ts +++ b/api/src/service/provisioning_start.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -12,6 +13,8 @@ export async function setProvisioningStartFlag( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Set Flag to signal start of provisioning"); + const provisioningStartEventResult = await Cache.withCache(conn, ctx, async (cache) => ProvisioningStart.setProvisioningStartFlag(ctx, serviceUser), ); diff --git a/api/src/service/public_key_get.ts b/api/src/service/public_key_get.ts index 1aea3c462..53faeebf8 100644 --- a/api/src/service/public_key_get.ts +++ b/api/src/service/public_key_get.ts @@ -4,12 +4,15 @@ import * as Cache from "./cache2"; import { ConnToken } from "./conn"; import * as PublicKeyGet from "./domain/organization/public_key_get"; import { PublicKeyBase64 } from "./domain/organization/public_key"; +import logger from "lib/logger"; export async function getPublicKey( conn: ConnToken, ctx: Ctx, organization: string, ): Promise> { + logger.debug("Getting public key"); + return await Cache.withCache(conn, ctx, async (cache) => PublicKeyGet.getPublicKey(ctx, organization, { getPublicKeysEvents: async () => cache.getPublicKeyEvents(), diff --git a/api/src/service/public_key_publish.ts b/api/src/service/public_key_publish.ts index 018da8548..c5e5676f7 100644 --- a/api/src/service/public_key_publish.ts +++ b/api/src/service/public_key_publish.ts @@ -21,6 +21,8 @@ export async function publishPublicKey( serviceUser: ServiceUser, requestData: PublicKeyPublish.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Publishing public key"); + const publicKeyPublishResult = await Cache.withCache(conn, ctx, async (cache) => PublicKeyPublish.publishPublicKey(ctx, serviceUser, requestData, { publicKeyAlreadyExists: async (organization) => diff --git a/api/src/service/public_key_update.ts b/api/src/service/public_key_update.ts index 9263f2ff7..b1d76fa7c 100644 --- a/api/src/service/public_key_update.ts +++ b/api/src/service/public_key_update.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -20,6 +21,8 @@ export async function updatePublicKey( serviceUser: ServiceUser, requestData: PublicKeyUpdate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Updating public key"); + const publicKeyUpdateResult = await Cache.withCache(conn, ctx, async (cache) => PublicKeyUpdate.updatePublicKey(ctx, serviceUser, requestData, { getPublicKey: async (organization) => diff --git a/api/src/service/storage_service_url_get.ts b/api/src/service/storage_service_url_get.ts index af3098289..7761020d2 100644 --- a/api/src/service/storage_service_url_get.ts +++ b/api/src/service/storage_service_url_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -10,6 +11,8 @@ export async function storageServiceUrlGet( ctx: Ctx, organization: string, ): Promise> { + logger.debug("Getting storage service url"); + const storageServiceUrlResult = await Cache.withCache(conn, ctx, async (cache) => { return GetStorageServiceUrl.getStorageServiceUrl(organization, { getStorageServiceUrlPublishedEvents: async () => { diff --git a/api/src/service/storage_service_url_update.ts b/api/src/service/storage_service_url_update.ts index 60f14f8a8..113f5bd96 100644 --- a/api/src/service/storage_service_url_update.ts +++ b/api/src/service/storage_service_url_update.ts @@ -6,6 +6,7 @@ import { ConnToken } from "./conn"; import { store } from "./store"; import * as PublishStorageServiceUrl from "./domain/document/storage_service_url_update"; import { ServiceUser } from "./domain/organization/service_user"; +import logger from "lib/logger"; export async function storageServiceUrlPublish( conn: ConnToken, @@ -13,6 +14,8 @@ export async function storageServiceUrlPublish( serviceUser: ServiceUser, requestData: PublishStorageServiceUrl.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Updating storage service url"); + const updateOrganizationUrlResult = await Cache.withCache(conn, ctx, async (cache) => { return PublishStorageServiceUrl.storageServiceUrlPublish(ctx, serviceUser, requestData); }); diff --git a/api/src/service/subproject_assign.ts b/api/src/service/subproject_assign.ts index 491d291ae..d66619267 100644 --- a/api/src/service/subproject_assign.ts +++ b/api/src/service/subproject_assign.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -19,6 +20,8 @@ export async function assignSubproject( subprojectId: Subproject.Id, assignee: Identity, ): Promise> { + logger.debug({ projectId, subprojectId, assignee }, "Assigning subproject"); + const assignSubprojectResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectAssign.assignSubproject(ctx, serviceUser, projectId, subprojectId, assignee, { getSubproject: async () => { diff --git a/api/src/service/subproject_close.ts b/api/src/service/subproject_close.ts index 82ea83de4..b2bc9e295 100644 --- a/api/src/service/subproject_close.ts +++ b/api/src/service/subproject_close.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -17,6 +18,8 @@ export async function closeSubproject( projectId: Project.Id, subprojectId: Subproject.Id, ): Promise> { + logger.debug({ projectId, subprojectId }, "Closing Subproject"); + const closeSubprojectResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectClose.closeSubproject(ctx, serviceUser, projectId, subprojectId, { getSubproject: async (pId, spId) => { diff --git a/api/src/service/subproject_create.ts b/api/src/service/subproject_create.ts index 056777259..b15c4e136 100644 --- a/api/src/service/subproject_create.ts +++ b/api/src/service/subproject_create.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -16,6 +17,8 @@ export async function createSubproject( serviceUser: ServiceUser, requestData: Subproject.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Creating Subproject"); + const createEventResult = await Cache.withCache(conn, ctx, (cache) => { return Subproject.createSubproject(ctx, serviceUser, requestData, { subprojectExists: async (projectId, subprojectId) => { diff --git a/api/src/service/subproject_get.ts b/api/src/service/subproject_get.ts index f1b8a8715..840e2e918 100644 --- a/api/src/service/subproject_get.ts +++ b/api/src/service/subproject_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -15,6 +16,8 @@ export async function getSubproject( projectId: Project.Id, subprojectId: Subproject.Id, ): Promise> { + logger.debug({ projectId, subprojectId }, "Getting subproject"); + const subprojectResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectGet.getSubproject(ctx, serviceUser, subprojectId, { getSubproject: async () => { diff --git a/api/src/service/subproject_history_get.ts b/api/src/service/subproject_history_get.ts index 7799b06d2..5bd03a8f7 100644 --- a/api/src/service/subproject_history_get.ts +++ b/api/src/service/subproject_history_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import VError = require("verror"); import { Ctx } from "../lib/ctx"; @@ -19,6 +20,8 @@ export async function getSubprojectHistory( subprojectId: Subproject.Id, filter?: History.Filter, ): Promise> { + logger.debug({ projectId, subprojectId, filter }, "Getting subproject history"); + const subprojectHistoryResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectHistory.getHistory( ctx, diff --git a/api/src/service/subproject_list.ts b/api/src/service/subproject_list.ts index 0137a30a0..1404ef47c 100644 --- a/api/src/service/subproject_list.ts +++ b/api/src/service/subproject_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -14,6 +15,8 @@ export async function listSubprojects( serviceUser: ServiceUser, projectId: Project.Id, ): Promise> { + logger.debug({ projectId }, "Listing subprojects"); + const visibleSubprojectsResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectList.getAllVisible(ctx, serviceUser, { getAllSubprojects: async () => { diff --git a/api/src/service/subproject_permission_grant.ts b/api/src/service/subproject_permission_grant.ts index a645a4116..aa038b037 100644 --- a/api/src/service/subproject_permission_grant.ts +++ b/api/src/service/subproject_permission_grant.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -22,6 +23,8 @@ export async function grantSubprojectPermission( grantee: Identity, intent: Intent, ): Promise> { + logger.debug({ grantee, intent, projectId, subprojectId }, "Granting subproject permission"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectPermissionGrant.grantSubprojectPermission( ctx, diff --git a/api/src/service/subproject_permission_revoke.ts b/api/src/service/subproject_permission_revoke.ts index dd12f83d3..d20265d6a 100644 --- a/api/src/service/subproject_permission_revoke.ts +++ b/api/src/service/subproject_permission_revoke.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -22,6 +23,8 @@ export async function revokeSubprojectPermission( revokee: Identity, intent: Intent, ): Promise> { + logger.debug({ revokee, intent, projectId, subprojectId }, "Revoking subproject permission"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectPermissionRevoke.revokeSubprojectPermission( ctx, @@ -37,9 +40,11 @@ export async function revokeSubprojectPermission( }, ), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "close project failed"); } + const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/subproject_permissions_list.ts b/api/src/service/subproject_permissions_list.ts index e6e506425..180d5437e 100644 --- a/api/src/service/subproject_permissions_list.ts +++ b/api/src/service/subproject_permissions_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -16,6 +17,8 @@ export async function listSubprojectPermissions( projectId: Project.Id, subprojectId: Subproject.Id, ): Promise> { + logger.debug({ projectId, subprojectId }, "Getting subproject permissions"); + const subprojectPermissionsResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectPermissionsList.getSubprojectPermissions(ctx, serviceUser, projectId, subprojectId, { getSubproject: async (pId, spId) => { diff --git a/api/src/service/subproject_projected_budget_delete.ts b/api/src/service/subproject_projected_budget_delete.ts index 2108a48db..2ea10ecbe 100644 --- a/api/src/service/subproject_projected_budget_delete.ts +++ b/api/src/service/subproject_projected_budget_delete.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -21,6 +22,8 @@ export async function deleteProjectedBudget( organization: string, currencyCode: CurrencyCode, ): Promise> { + logger.debug({ projectId, subprojectId, organization, currencyCode }, "Deleting project budget"); + const deleteProjectedBudgetResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectProjectedBudgetDelete.deleteProjectedBudget( ctx, diff --git a/api/src/service/subproject_projected_budget_update.ts b/api/src/service/subproject_projected_budget_update.ts index 912777f56..588b4efc9 100644 --- a/api/src/service/subproject_projected_budget_update.ts +++ b/api/src/service/subproject_projected_budget_update.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -22,6 +23,11 @@ export async function updateProjectedBudget( value: MoneyAmount, currencyCode: CurrencyCode, ): Promise> { + logger.debug( + { projectId, subprojectId, organization, value, currencyCode }, + "Updating project budget", + ); + const updateProjectedBudgetResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectProjectedBudgetUpdate.updateProjectedBudget( ctx, @@ -41,10 +47,13 @@ export async function updateProjectedBudget( }, ), ); + if (Result.isErr(updateProjectedBudgetResult)) { return new VError(updateProjectedBudgetResult, "delete projected budget of subproject failed"); } + const { newEvents, projectedBudgets } = updateProjectedBudgetResult; + for (const event of newEvents) { await store(conn, ctx, event, serviceUser.address); } diff --git a/api/src/service/subproject_update.ts b/api/src/service/subproject_update.ts index 0d6b774e7..d19f9731d 100644 --- a/api/src/service/subproject_update.ts +++ b/api/src/service/subproject_update.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -18,6 +19,8 @@ export async function updateSubproject( subprojectId: Subproject.Id, requestData: SubprojectUpdate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Updating subproject"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => SubprojectUpdate.updateSubproject(ctx, serviceUser, projectId, subprojectId, requestData, { getSubproject: async (pId, spId) => { @@ -28,6 +31,7 @@ export async function updateSubproject( }, }), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "close project failed"); } diff --git a/api/src/service/user_assignments_get.ts b/api/src/service/user_assignments_get.ts index c4420e8e0..91ac3fe85 100644 --- a/api/src/service/user_assignments_get.ts +++ b/api/src/service/user_assignments_get.ts @@ -7,6 +7,7 @@ import * as UserAssignmentsGet from "./domain/workflow/user_assignments_get"; import * as UserAssignments from "./domain/workflow/user_assignments"; import * as UserQuery from "./user_query"; import { ServiceUser } from "./domain/organization/service_user"; +import logger from "lib/logger"; export async function getUserAssignments( conn: ConnToken, @@ -15,6 +16,8 @@ export async function getUserAssignments( issuerOrganization: string, requestData: UserAssignmentsGet.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Get user assignments"); + const userAssignmentResult = await Cache.withCache( conn, ctx, diff --git a/api/src/service/user_authenticate.ts b/api/src/service/user_authenticate.ts index 6e3eafa2d..09ffa4c4a 100644 --- a/api/src/service/user_authenticate.ts +++ b/api/src/service/user_authenticate.ts @@ -1,23 +1,23 @@ import { VError } from "verror"; import { ConnToken } from "."; import { globalIntents } from "../authz/intents"; +import { config } from "../config"; import { Ctx } from "../lib/ctx"; +import logger from "../lib/logger"; import * as SymmetricCrypto from "../lib/symmetricCrypto"; import { getOrganizationAddress } from "../organization/organization"; import * as Result from "../result"; +import { NotAuthorized } from "./domain/errors/not_authorized"; import * as AuthToken from "./domain/organization/auth_token"; +import * as UserRecord from "./domain/organization/user_record"; import { AuthenticationFailed } from "./errors/authentication_failed"; -import { NotAuthorized } from "./domain/errors/not_authorized"; +import { getselfaddress } from "./getselfaddress"; import { getGlobalPermissions } from "./global_permissions_get"; +import { grantpermissiontoaddress } from "./grantpermissiontoaddress"; import { getGroupsForUser } from "./group_query"; import { importprivkey } from "./importprivkey"; import { hashPassword, isPasswordMatch } from "./password"; -import logger from "../lib/logger"; import * as UserQuery from "./user_query"; -import * as UserRecord from "./domain/organization/user_record"; -import { grantpermissiontoaddress } from "./grantpermissiontoaddress"; -import { config } from "../config"; -import { getselfaddress } from "./getselfaddress"; export interface UserLoginResponse { id: string; @@ -37,6 +37,7 @@ export async function authenticate( userId: string, password: string, ): Promise> { + logger.debug({ userId, organization }, "Authenticating user"); // The special "root" user is not on the chain: if (userId === "root") { const tokenResult = await authenticateRoot(conn, ctx, organization, rootSecret, password); @@ -64,6 +65,8 @@ async function authenticateRoot( rootSecret: string, password: string, ): Promise> { + logger.debug("Authenticating Root user"); + if (typeof conn.multichainClient === "undefined") { logger.error("Received request, but MultiChain connection/permissions not ready yet."); return new AuthenticationFailed( diff --git a/api/src/service/user_create.ts b/api/src/service/user_create.ts index a35d7eaec..1a927201c 100644 --- a/api/src/service/user_create.ts +++ b/api/src/service/user_create.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import { encrypt } from "../lib/symmetricCrypto"; @@ -22,6 +23,8 @@ export async function createUser( serviceUser: ServiceUser, requestData: UserCreate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Creating user"); + const newEventsResult = await UserCreate.createUser(ctx, serviceUser, requestData, { getGlobalPermissions: async () => getGlobalPermissions(conn, ctx, serviceUser), userExists: async (userId) => userExists(conn, ctx, serviceUser, userId), @@ -32,6 +35,7 @@ export async function createUser( encrypt: async (plaintext) => encrypt(organizationSecret, plaintext), groupExists: async (userId) => GroupQuery.groupExists(conn, ctx, serviceUser, userId), }); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "failed to create user"); } diff --git a/api/src/service/user_disable.ts b/api/src/service/user_disable.ts index d656f4823..73a0c37e0 100644 --- a/api/src/service/user_disable.ts +++ b/api/src/service/user_disable.ts @@ -9,6 +9,7 @@ import { getUserAssignments } from "./user_assignments_get"; import { store } from "./store"; import * as UserQuery from "./user_query"; import { VError } from "verror"; +import logger from "lib/logger"; export async function disableUser( conn: ConnToken, @@ -17,6 +18,8 @@ export async function disableUser( issuerOrganization: string, revokee: UserDisable.RequestData, ): Promise> { + logger.debug({ revokee }, "Disabling user"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => UserDisable.disableUser(ctx, serviceUser, issuerOrganization, revokee, { getUser: () => UserQuery.getUser(conn, ctx, serviceUser, revokee.userId), @@ -25,6 +28,7 @@ export async function disableUser( getUserAssignments(conn, ctx, serviceUser, issuerOrganization, revokee), }), ); + if (Result.isErr(newEventsResult)) return new VError(newEventsResult, "failed to disable user"); const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/user_enable.ts b/api/src/service/user_enable.ts index 785e47f5a..3e0da01fe 100644 --- a/api/src/service/user_enable.ts +++ b/api/src/service/user_enable.ts @@ -7,19 +7,30 @@ import { getGlobalPermissions } from "./global_permissions_get"; import { store } from "./store"; import * as UserQuery from "./user_query"; import { VError } from "verror"; +import logger from "lib/logger"; export async function enableUser( conn: ConnToken, ctx: Ctx, serviceUser: ServiceUser, issuerOrganization: string, - revokee: UserEnable.RequestData, + grantee: UserEnable.RequestData, ): Promise> { - const newEventsResult = await UserEnable.enableUser(ctx, serviceUser, issuerOrganization, revokee, { - getUser: () => UserQuery.getUser(conn, ctx, serviceUser, revokee.userId), - getGlobalPermissions: async () => getGlobalPermissions(conn, ctx, serviceUser), - }); + logger.debug({ grantee }, "Enable user"); + + const newEventsResult = await UserEnable.enableUser( + ctx, + serviceUser, + issuerOrganization, + grantee, + { + getUser: () => UserQuery.getUser(conn, ctx, serviceUser, grantee.userId), + getGlobalPermissions: async () => getGlobalPermissions(conn, ctx, serviceUser), + }, + ); + if (Result.isErr(newEventsResult)) return new VError(newEventsResult, "failed to enable user"); + const newEvents = newEventsResult; for (const event of newEvents) { await store(conn, ctx, event, serviceUser.address); diff --git a/api/src/service/user_password_change.ts b/api/src/service/user_password_change.ts index 7c16f8492..d02b9a2b5 100644 --- a/api/src/service/user_password_change.ts +++ b/api/src/service/user_password_change.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -15,6 +16,8 @@ export async function changeUserPassword( issuerOrganization: string, requestData: UserPasswordChange.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Changing user password"); + const newEventsResult = await UserPasswordChange.changeUserPassword( ctx, serviceUser, @@ -25,6 +28,7 @@ export async function changeUserPassword( hash: (passwordPlainText) => hashPassword(passwordPlainText), }, ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "failed to change password"); } diff --git a/api/src/service/user_permission_grant.ts b/api/src/service/user_permission_grant.ts index 4c493cda1..c7a064248 100644 --- a/api/src/service/user_permission_grant.ts +++ b/api/src/service/user_permission_grant.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -20,6 +21,8 @@ export async function grantUserPermission( grantee: Identity, intent: Intent, ): Promise> { + logger.debug({ grantee, intent, userId }, "Granting user permission"); + const newEventsResult = await UserPermissionGrant.grantUserPermission( ctx, serviceUser, diff --git a/api/src/service/user_permission_revoke.ts b/api/src/service/user_permission_revoke.ts index e9f9dbb9a..57236b47b 100644 --- a/api/src/service/user_permission_revoke.ts +++ b/api/src/service/user_permission_revoke.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -21,6 +22,8 @@ export async function revokeUserPermission( revokee: Identity, intent: Intent, ): Promise> { + logger.debug({ revokee, intent, userId }, "Revoking user permission"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => UserPermissionRevoke.revokeUserPermission(ctx, serviceUser, userId, revokee, intent, { getTargetUser: (id) => UserQuery.getUser(conn, ctx, serviceUser, id), diff --git a/api/src/service/user_permissions_list.ts b/api/src/service/user_permissions_list.ts index 69b0daeb3..79a93e8fb 100644 --- a/api/src/service/user_permissions_list.ts +++ b/api/src/service/user_permissions_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -14,13 +15,17 @@ export async function getUserPermissions( serviceUser: ServiceUser, userId: UserRecord.Id, ): Promise> { + logger.debug({ userId }, "Getting user permission"); + const userResult = await Cache.withCache(conn, ctx, async (cache) => UserGet.getOneUser(ctx, serviceUser, userId, { getUserEvents: async () => cache.getUserEvents(userId), }), ); + if (Result.isErr(userResult)) { return new VError(userResult, `failed to get user ${userId}`); } + return userResult.permissions; } diff --git a/api/src/service/user_query.ts b/api/src/service/user_query.ts index 47acfbf3a..1d72fe830 100644 --- a/api/src/service/user_query.ts +++ b/api/src/service/user_query.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -13,6 +14,8 @@ export async function getUsers( ctx: Ctx, serviceUser: ServiceUser, ): Promise> { + logger.debug("Getting all users"); + const usersResult = await Cache.withCache(conn, ctx, async (cache) => UserGet.getAllUsers(ctx, serviceUser, { getUserEvents: async () => { @@ -29,10 +32,13 @@ export async function getUser( serviceUser: ServiceUser, userId: UserRecord.Id, ): Promise> { + logger.debug({ userId }, "Getting user by id"); + const usersResult = await getUsers(conn, ctx, serviceUser); if (Result.isErr(usersResult)) { return new VError(usersResult, "could not fetch users"); } + const user = usersResult.find((x) => x.id === userId); if (user === undefined) { return new NotFound(ctx, "user", userId); @@ -46,6 +52,8 @@ export async function userExists( serviceUser: ServiceUser, userId: UserRecord.Id, ): Promise> { + logger.debug({ userId }, "Checking existance of user by id"); + const usersResult = await getUsers(conn, ctx, serviceUser); if (Result.isErr(usersResult)) { return new VError(usersResult, "could not fetch users"); diff --git a/api/src/service/workflowitem_assign.ts b/api/src/service/workflowitem_assign.ts index f4f52fba4..60ad684a7 100644 --- a/api/src/service/workflowitem_assign.ts +++ b/api/src/service/workflowitem_assign.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -23,6 +24,11 @@ export async function assignWorkflowitem( workflowitemId: Workflowitem.Id, assignee: Identity, ): Promise> { + logger.debug( + { assignee, projectId, subprojectId, workflowitemId }, + "Assigning workflowitem to user", + ); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => { return WorkflowitemAssign.assignWorkflowitem( ctx, diff --git a/api/src/service/workflowitem_close.ts b/api/src/service/workflowitem_close.ts index 3105bfe03..c9961f7f2 100644 --- a/api/src/service/workflowitem_close.ts +++ b/api/src/service/workflowitem_close.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -22,6 +23,8 @@ export async function closeWorkflowitem( workflowitemId: Workflowitem.Id, rejectReason?: string, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId, rejectReason }, "Closing workflowitem"); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemClose.closeWorkflowitem( ctx, diff --git a/api/src/service/workflowitem_create.ts b/api/src/service/workflowitem_create.ts index 16abc5ea9..b9152d578 100644 --- a/api/src/service/workflowitem_create.ts +++ b/api/src/service/workflowitem_create.ts @@ -1,6 +1,6 @@ import { VError } from "verror"; -import { encryptWithKey } from "../lib/asymmetricCrypto"; -import { Ctx } from "../lib/ctx"; +import { encryptWithKey } from "lib/asymmetricCrypto"; +import { Ctx } from "lib/ctx"; import * as Result from "../result"; import * as Cache from "./cache2"; import { StorageServiceClientI } from "./Client_storage_service.h"; @@ -19,6 +19,7 @@ import * as TypeEvents from "./domain/workflowitem_types/apply_workflowitem_type import * as PublicKeyGet from "./public_key_get"; import * as UserQuery from "./user_query"; import { store } from "./store"; +import logger from "lib/logger"; export { RequestData } from "./domain/workflow/workflowitem_create"; @@ -29,6 +30,7 @@ export async function createWorkflowitem( serviceUser: ServiceUser, requestData: WorkflowitemCreate.RequestData, ): Promise> { + logger.debug({ req: requestData }, "Creating workflowitem"); const newEventResult = await Cache.withCache(conn, ctx, (cache) => { return WorkflowitemCreate.createWorkflowitem(ctx, serviceUser, requestData, { workflowitemExists: async ( diff --git a/api/src/service/workflowitem_document_download.ts b/api/src/service/workflowitem_document_download.ts index ae2afb653..9a65ddf85 100644 --- a/api/src/service/workflowitem_document_download.ts +++ b/api/src/service/workflowitem_document_download.ts @@ -16,6 +16,7 @@ import * as Project from "./domain/workflow/project"; import * as Subproject from "./domain/workflow/subproject"; import * as Workflowitem from "./domain/workflow/workflowitem"; import VError = require("verror"); +import logger from "lib/logger"; export async function getDocument( conn: ConnToken, @@ -27,6 +28,8 @@ export async function getDocument( workflowitemId: Workflowitem.Id, documentId: string, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId, documentId }, "Getting document"); + const documentResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemDocumentDownload.getDocument(ctx, serviceUser, workflowitemId, documentId, { getWorkflowitem: async () => { diff --git a/api/src/service/workflowitem_get.ts b/api/src/service/workflowitem_get.ts index 534a1863b..6eac86a17 100644 --- a/api/src/service/workflowitem_get.ts +++ b/api/src/service/workflowitem_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -17,6 +18,8 @@ export async function getWorkflowitem( subprojectId: Subproject.Id, workflowitemId: Workflowitem.Id, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId }, "Getting workflowitem"); + const workflowitemResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemGet.getWorkflowitem(ctx, serviceUser, workflowitemId, { getWorkflowitem: async () => { @@ -24,6 +27,7 @@ export async function getWorkflowitem( }, }), ); + return Result.mapErr( workflowitemResult, (err) => new VError(err, `could not fetch workflowitem ${workflowitemId}`), diff --git a/api/src/service/workflowitem_get_details.ts b/api/src/service/workflowitem_get_details.ts index bc5927305..03c87d64f 100644 --- a/api/src/service/workflowitem_get_details.ts +++ b/api/src/service/workflowitem_get_details.ts @@ -11,6 +11,7 @@ import * as WorkflowitemGetDetails from "./domain/workflow/workflowitem_get_deta import * as WorkflowitemGet from "./domain/workflow/workflowitem_get"; import * as WorkflowitemDocumentDownloadService from "./workflowitem_document_download"; import { StorageServiceClientI } from "./Client_storage_service.h"; +import logger from "lib/logger"; export async function getWorkflowitemDetails( conn: ConnToken, @@ -21,6 +22,8 @@ export async function getWorkflowitemDetails( subprojectId: Subproject.Id, workflowitemId: Workflowitem.Id, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId }, "Getting workflowitem details"); + const workflowitemResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemGetDetails.getWorkflowitemDetails(ctx, serviceUser, workflowitemId, { getWorkflowitem: async () => { diff --git a/api/src/service/workflowitem_history_get.ts b/api/src/service/workflowitem_history_get.ts index 8af3029d3..737efd204 100644 --- a/api/src/service/workflowitem_history_get.ts +++ b/api/src/service/workflowitem_history_get.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import VError = require("verror"); import { Ctx } from "../lib/ctx"; @@ -21,6 +22,8 @@ export async function getWorkflowitemHistory( workflowitemId: Workflowitem.Id, filter?: History.Filter, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId, filter }, "Getting workflowitem history"); + const workflowitemHistoryResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemHistory.getHistory( ctx, @@ -36,6 +39,7 @@ export async function getWorkflowitemHistory( filter, ), ); + return Result.mapErr( workflowitemHistoryResult, (err) => new VError(err, `could not get history of workflowitem with id ${workflowitemId}`), diff --git a/api/src/service/workflowitem_list.ts b/api/src/service/workflowitem_list.ts index 4327e9596..276f5917e 100644 --- a/api/src/service/workflowitem_list.ts +++ b/api/src/service/workflowitem_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -14,6 +15,8 @@ export async function listWorkflowitems( projectId: string, subprojectId: string, ): Promise> { + logger.debug({ projectId, subprojectId }, "Getting all workflowitems"); + const workflowitemsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemList.getAllVisible(ctx, serviceUser, projectId, subprojectId, { getWorkflowitems: async (pId, spId) => { diff --git a/api/src/service/workflowitem_permission_grant.ts b/api/src/service/workflowitem_permission_grant.ts index cf2085c2b..82aca0bfe 100644 --- a/api/src/service/workflowitem_permission_grant.ts +++ b/api/src/service/workflowitem_permission_grant.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { config } from "../config"; @@ -32,6 +33,11 @@ export async function grantWorkflowitemPermission( grantee: Identity, intent: Intent, ): Promise> { + logger.debug( + { grantee, intent, projectId, subprojectId, workflowitemId }, + "Granting workflowitem permission", + ); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemPermissionGrant.grantWorkflowitemPermission( ctx, @@ -93,9 +99,11 @@ export async function grantWorkflowitemPermission( }, ), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "permission grant failed"); } + const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/workflowitem_permission_revoke.ts b/api/src/service/workflowitem_permission_revoke.ts index f9a85f61d..9d243c4ae 100644 --- a/api/src/service/workflowitem_permission_revoke.ts +++ b/api/src/service/workflowitem_permission_revoke.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import Intent from "../authz/intents"; import { Ctx } from "../lib/ctx"; @@ -24,6 +25,11 @@ export async function revokeWorkflowitemPermission( revokee: Identity, intent: Intent, ): Promise> { + logger.debug( + { revokee, intent, projectId, subprojectId, workflowitemId }, + "Revoking workflowitem permission", + ); + const newEventsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemPermissionRevoke.revokeWorkflowitemPermission( ctx, @@ -40,9 +46,11 @@ export async function revokeWorkflowitemPermission( }, ), ); + if (Result.isErr(newEventsResult)) { return new VError(newEventsResult, "close project failed"); } + const newEvents = newEventsResult; for (const event of newEvents) { diff --git a/api/src/service/workflowitem_permissions_list.ts b/api/src/service/workflowitem_permissions_list.ts index db2c0afe5..32d2876cb 100644 --- a/api/src/service/workflowitem_permissions_list.ts +++ b/api/src/service/workflowitem_permissions_list.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -20,6 +21,8 @@ export async function listWorkflowitemPermissions( subprojectId: Subproject.Id, workflowitemId: Workflowitem.Id, ): Promise> { + logger.debug({ projectId, subprojectId, workflowitemId }, "Getting workflowitem permissions"); + const permissionsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemPermissionsList.getAll(ctx, serviceUser, projectId, subprojectId, workflowitemId, { getWorkflowitem: async (pId, spId, wId) => { @@ -27,6 +30,7 @@ export async function listWorkflowitemPermissions( }, }), ); + return Result.mapErr( permissionsResult, (err) => new VError(err, `could not fetch permissions of ${workflowitemId} `), diff --git a/api/src/service/workflowitem_update.ts b/api/src/service/workflowitem_update.ts index d45c84024..62be017c0 100644 --- a/api/src/service/workflowitem_update.ts +++ b/api/src/service/workflowitem_update.ts @@ -21,6 +21,7 @@ import * as DocumentShare from "./document_share"; import { config } from "../config"; import { store } from "./store"; +import logger from "lib/logger"; export type RequestData = WorkflowitemUpdate.RequestData; @@ -35,6 +36,10 @@ export async function updateWorkflowitem( modification: WorkflowitemUpdate.RequestData, ): Promise> { const updateWorkflowitemResult = await Cache.withCache(conn, ctx, async (cache) => { + logger.debug( + { projectId, subprojectId, workflowitemId, modification }, + "Updating workflowitem", + ); return WorkflowitemUpdate.updateWorkflowitem( ctx, serviceUser, @@ -102,12 +107,15 @@ export async function updateWorkflowitem( for (const event of newEvents) { await store(conn, ctx, event, serviceUser.address); } + const workflowitem = await Cache.withCache(conn, ctx, async (cache) => cache.getWorkflowitem(projectId, subprojectId, workflowitemId), ); + if (Result.isErr(workflowitem)) { return new VError(workflowitem, `failed to get workflowitem ${workflowitemId}`); } + if ( config.documentFeatureEnabled && modification.documents && @@ -117,9 +125,11 @@ export async function updateWorkflowitem( const users = workflowitem.permissions["workflowitem.view"]; if (users) { const organizations = await getOrganizations(users); + if (Result.isErr(organizations)) { return new VError(organizations, "failed to get organizations"); } + for (const organization of organizations) { const event = await DocumentShare.documentShare(conn, ctx, serviceUser, { organization, @@ -135,15 +145,19 @@ export async function updateWorkflowitem( } } } + async function getOrganizations(users: string[]): Promise> { - const organizations: string[] = []; + logger.debug("Gathering Organizatinos based on users"); + const organizations: string[] = []; for (const userId of users) { const user = await UserQuery.getUser(conn, ctx, serviceUser, userId); if (Result.isErr(user)) { return new VError(user, "failed to get user"); } + const { organization } = user; + if (!organizations.includes(organization)) { organizations.push(organization); } diff --git a/api/src/service/workflowitems_reorder.ts b/api/src/service/workflowitems_reorder.ts index 0ae45daf4..f58159750 100644 --- a/api/src/service/workflowitems_reorder.ts +++ b/api/src/service/workflowitems_reorder.ts @@ -1,3 +1,4 @@ +import logger from "lib/logger"; import { VError } from "verror"; import { Ctx } from "../lib/ctx"; import * as Result from "../result"; @@ -18,6 +19,8 @@ export async function setWorkflowitemOrdering( subprojectId: Subproject.Id, ordering: WorkflowitemOrdering, ): Promise> { + logger.debug({ ordering, projectId, subprojectId }, "Setting workflowitem ordering"); + const reorderWorkflowitemsResult = await Cache.withCache(conn, ctx, async (cache) => WorkflowitemsReorder.setWorkflowitemOrdering( ctx, diff --git a/api/src/subproject_assign.ts b/api/src/subproject_assign.ts index 90cc4e0cc..17e57d7cd 100644 --- a/api/src/subproject_assign.ts +++ b/api/src/subproject_assign.ts @@ -107,6 +107,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to assign subproject")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -128,6 +129,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while assigning subproject"); reply.status(code).send(body); }); }); diff --git a/api/src/subproject_budget_delete_projected.ts b/api/src/subproject_budget_delete_projected.ts index e8deb6df5..756d179e4 100644 --- a/api/src/subproject_budget_delete_projected.ts +++ b/api/src/subproject_budget_delete_projected.ts @@ -129,6 +129,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to delete projected budget"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -155,6 +156,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while deleting projected subproject budget"); reply.status(code).send(body); }); }, diff --git a/api/src/subproject_budget_update_projected.ts b/api/src/subproject_budget_update_projected.ts index 34edabba3..f5fb67feb 100644 --- a/api/src/subproject_budget_update_projected.ts +++ b/api/src/subproject_budget_update_projected.ts @@ -141,6 +141,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to update projected budget"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -175,6 +176,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while updating projected subproject budget"); reply.status(code).send(body); }); }, diff --git a/api/src/subproject_close.ts b/api/src/subproject_close.ts index dd995efbe..868245a82 100644 --- a/api/src/subproject_close.ts +++ b/api/src/subproject_close.ts @@ -103,6 +103,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to close project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -124,6 +125,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while closing subproject"); reply.status(code).send(body); }); }); diff --git a/api/src/subproject_create.ts b/api/src/subproject_create.ts index cd6f85b91..247d832d8 100644 --- a/api/src/subproject_create.ts +++ b/api/src/subproject_create.ts @@ -182,6 +182,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to create subproject")); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid Request body"); return; } @@ -216,6 +217,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi .catch((err) => { const { code, body } = toHttpError(err); reply.status(code).send(body); + request.log.error(err, "Error while creating Sub-Project"); }); }, ); diff --git a/api/src/subproject_list.ts b/api/src/subproject_list.ts index 55b252db7..f92f42021 100644 --- a/api/src/subproject_list.ts +++ b/api/src/subproject_list.ts @@ -178,13 +178,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -227,6 +229,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing subprojects"); reply.status(code).send(body); }); }); diff --git a/api/src/subproject_permission_grant.ts b/api/src/subproject_permission_grant.ts index 4f1f508ae..0abc90fb0 100644 --- a/api/src/subproject_permission_grant.ts +++ b/api/src/subproject_permission_grant.ts @@ -118,6 +118,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to grant project permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -138,6 +139,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while granting subproject permission"); reply.status(code).send(body); }); }, diff --git a/api/src/subproject_permission_revoke.ts b/api/src/subproject_permission_revoke.ts index 9646ab755..048070685 100644 --- a/api/src/subproject_permission_revoke.ts +++ b/api/src/subproject_permission_revoke.ts @@ -118,6 +118,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to revoke project permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -139,6 +140,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while revoking subproject permission"); reply.status(code).send(body); }); }, diff --git a/api/src/subproject_permissions_list.ts b/api/src/subproject_permissions_list.ts index f253a6166..15e5bb0a0 100644 --- a/api/src/subproject_permissions_list.ts +++ b/api/src/subproject_permissions_list.ts @@ -85,26 +85,33 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; + reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const subprojectId = request.query.subprojectId; if (!isNonemptyString(subprojectId)) { + const message = + "required query parameter `subprojectId` not present (must be non-empty string)"; + reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `subprojectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -134,6 +141,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing subproject permissions"); reply.status(code).send(body); } }, diff --git a/api/src/subproject_update.ts b/api/src/subproject_update.ts index 7ef2cfbfa..fa42be2aa 100644 --- a/api/src/subproject_update.ts +++ b/api/src/subproject_update.ts @@ -117,6 +117,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to update project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -143,6 +144,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while updating subproject"); reply.status(code).send(body); }); }); diff --git a/api/src/subproject_view_details.ts b/api/src/subproject_view_details.ts index 7556fe098..4ab352cc5 100644 --- a/api/src/subproject_view_details.ts +++ b/api/src/subproject_view_details.ts @@ -200,26 +200,31 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const subprojectId = request.query.subprojectId; if (!isNonemptyString(subprojectId)) { + const message = + "required query parameter `subprojectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `subprojectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -259,6 +264,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }; // Get info of workflowitems + request.log.debug({ exposedProject }, "Collecting workflowitems of subproject"); const workflowitemsResult = await service.getWorkflowitems( ctx, user, @@ -306,6 +312,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting subproject details"); reply.status(code).send(body); } }, diff --git a/api/src/subproject_view_history.ts b/api/src/subproject_view_history.ts index a3c5da3c9..31beaefe8 100644 --- a/api/src/subproject_view_history.ts +++ b/api/src/subproject_view_history.ts @@ -158,26 +158,31 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const subprojectId = request.query.subprojectId; if (!isNonemptyString(subprojectId)) { + const message = + "required query parameter `subprojectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `subprojectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -186,13 +191,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.offset !== undefined) { offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { + const message = "if present, the query parameter `offset` must be an integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -202,13 +209,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -222,6 +231,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const subproject: Subproject.Subproject = subprojectResult; // Get log of workflowitems + request.log.debug({ subproject }, "Getting Workflowitems of subproject"); const workflowitemsResult = await service.getWorkflowitems( ctx, user, @@ -260,6 +270,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while viewing subproject history"); reply.status(code).send(body); } }, diff --git a/api/src/subproject_view_history_v2.ts b/api/src/subproject_view_history_v2.ts index 937e9bb1a..be0071ad3 100644 --- a/api/src/subproject_view_history_v2.ts +++ b/api/src/subproject_view_history_v2.ts @@ -1,4 +1,4 @@ -import { FastifyInstance, FastifyReply, RequestGenericInterface } from "fastify"; +import { FastifyInstance, FastifyReply, RequestGenericInterface, FastifyRequest } from "fastify"; import Joi = require("joi"); import VError = require("verror"); @@ -34,25 +34,22 @@ function validateRequestBody(body: any): Result.Type { /** * If no filter option is provided the return value is undefined */ -const createFilter = ( - reply: FastifyReply, - publisher?: Identity, - startAt?: string, - endAt?: string, - eventType?: string, -): History.Filter | undefined => { +const createFilter = (reply: FastifyReply, request: FastifyRequest): History.Filter | undefined => { + const { publisher, startAt, endAt, eventType } = request.query as Querystring; const noFilterSet = !publisher && !startAt && !endAt && !eventType; if (noFilterSet) return; if (publisher !== undefined) { if (!isNonemptyString(publisher)) { + const message = "if present, the query parameter `publisher` must be non-empty string"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `publisher` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } @@ -60,40 +57,47 @@ const createFilter = ( if (startAt !== undefined) { const startAtDate = new Date(startAt); if (isNaN(startAtDate.getTime())) { + const message = "if present, the query parameter `startAt` must be a valid ISO timestamp"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `startAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (endAt !== undefined) { const endAtDate = new Date(endAt); if (isNaN(endAtDate.getTime())) { + const message = "if present, the query parameter `endAt` must be a valid ISO timestamp"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `endAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (eventType !== undefined) { if (!isNonemptyString(eventType)) { + const message = "if present, the query parameter `eventType` must be non-empty string"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `eventType` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } + return { publisher, startAt, @@ -203,21 +207,19 @@ interface Service { ): Promise>; } -interface Request extends RequestGenericInterface { - Querystring: { - projectId: string; - subprojectId: string; - offset?: string; - limit?: string; - startAt?: string; - endAt?: string; - publisher?: string; - eventType?: string; - }; +interface Querystring extends RequestGenericInterface { + projectId: string; + subprojectId: string; + offset?: string; + limit?: string; + startAt?: string; + endAt?: string; + publisher?: string; + eventType?: string; } export function addHttpHandler(server: FastifyInstance, urlPrefix: string, service: Service) { - server.get( + server.get<{ Querystring: Querystring }>( `${urlPrefix}/subproject.viewHistory.v2`, mkSwaggerSchema(server), async (request, reply) => { @@ -231,26 +233,31 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const subprojectId = request.query.subprojectId; if (!isNonemptyString(subprojectId)) { + const message = + "required query parameter `subprojectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `subprojectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -259,13 +266,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.offset !== undefined) { offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { + const message = "if present, the query parameter `offset` must be an integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -275,24 +284,20 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } - const filter = createFilter( - reply, - request.query.publisher, - request.query.startAt, - request.query.endAt, - request.query.eventType, - ); + const filter = createFilter(reply, request); try { // Get all Events in subproject stream @@ -330,6 +335,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while viewing subproject history"); reply.status(code).send(body); } }, diff --git a/api/src/user_authenticate.ts b/api/src/user_authenticate.ts index 0331ddb79..51be8f607 100644 --- a/api/src/user_authenticate.ts +++ b/api/src/user_authenticate.ts @@ -184,6 +184,7 @@ export function addHttpHandler( if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "authentication failed")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -197,6 +198,7 @@ export function addHttpHandler( } default: // Joi validates only existing apiVersions + request.log.error({ err: bodyResult }, "Wrong api version specified"); assertUnreachable(bodyResult.apiVersion); } @@ -235,6 +237,7 @@ export function addHttpHandler( reply.status(200).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while user authenticate"); reply.status(code).send(body); } }); diff --git a/api/src/user_create.ts b/api/src/user_create.ts index 11018e007..aeffb1f89 100644 --- a/api/src/user_create.ts +++ b/api/src/user_create.ts @@ -153,6 +153,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to create user")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -171,6 +172,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi } default: // Joi validates only existing apiVersions + request.log.error({ err: bodyResult }, "Wrong api version specified"); assertUnreachable(bodyResult.apiVersion); } @@ -197,6 +199,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while creating user"); reply.status(code).send(body); }); }); diff --git a/api/src/user_disable.ts b/api/src/user_disable.ts index 29f30e01a..6cad8bbc6 100644 --- a/api/src/user_disable.ts +++ b/api/src/user_disable.ts @@ -99,6 +99,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to disable an user")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -120,6 +121,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while disabling user"); reply.status(code).send(body); }); }); diff --git a/api/src/user_enable.ts b/api/src/user_enable.ts index 63057c4fc..925b04365 100644 --- a/api/src/user_enable.ts +++ b/api/src/user_enable.ts @@ -99,6 +99,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to enable an user")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -120,6 +121,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while enabling user"); reply.status(code).send(body); }); }); diff --git a/api/src/user_list.ts b/api/src/user_list.ts index e1c2344e1..95a0d3cd0 100644 --- a/api/src/user_list.ts +++ b/api/src/user_list.ts @@ -115,6 +115,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing users"); reply.status(code).send(body); } }); diff --git a/api/src/user_listAssignments.ts b/api/src/user_listAssignments.ts index 99792a7c2..ea6616c48 100644 --- a/api/src/user_listAssignments.ts +++ b/api/src/user_listAssignments.ts @@ -82,13 +82,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const issuerOrganization: string = (request as AuthenticatedRequest).user.organization; if (!isNonemptyString(requestData.userId)) { + const message = "required query parameter `userId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `userId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -111,6 +113,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing assignments of user"); reply.status(code).send(body); } }, diff --git a/api/src/user_password_change.ts b/api/src/user_password_change.ts index ba9dd0721..c10161a01 100644 --- a/api/src/user_password_change.ts +++ b/api/src/user_password_change.ts @@ -104,6 +104,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to change user's password"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -129,6 +130,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while chaning user password"); reply.status(code).send(body); }); }); diff --git a/api/src/user_permission_grant.ts b/api/src/user_permission_grant.ts index 66bca6111..646b34872 100644 --- a/api/src/user_permission_grant.ts +++ b/api/src/user_permission_grant.ts @@ -119,6 +119,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to grant user permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -140,6 +141,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while granting user permission"); reply.status(code).send(body); }); }, diff --git a/api/src/user_permission_revoke.ts b/api/src/user_permission_revoke.ts index 7b69dd371..47ac8058d 100644 --- a/api/src/user_permission_revoke.ts +++ b/api/src/user_permission_revoke.ts @@ -119,6 +119,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to revoke user permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -140,6 +141,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while revoking user permission"); reply.status(code).send(body); }); }, diff --git a/api/src/user_permissions_list.ts b/api/src/user_permissions_list.ts index 3ac9ed751..bfe6052d8 100644 --- a/api/src/user_permissions_list.ts +++ b/api/src/user_permissions_list.ts @@ -80,13 +80,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const userId = request.query.userId; if (!isNonemptyString(userId)) { + const message = "required query parameter `userId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `userId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -105,6 +107,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing user permissions"); reply.status(code).send(body); } }, diff --git a/api/src/workflowitem_assign.ts b/api/src/workflowitem_assign.ts index 0a79f8c49..4c20fa002 100644 --- a/api/src/workflowitem_assign.ts +++ b/api/src/workflowitem_assign.ts @@ -111,6 +111,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to assign workflowitem")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -132,6 +133,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while assigning workflowitem"); reply.status(code).send(body); }); }); diff --git a/api/src/workflowitem_close.ts b/api/src/workflowitem_close.ts index 7686070bb..fcc14a4ca 100644 --- a/api/src/workflowitem_close.ts +++ b/api/src/workflowitem_close.ts @@ -112,6 +112,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to close worfklowitem")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -133,6 +134,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while closing workflowitem"); reply.status(code).send(body); }); }); diff --git a/api/src/workflowitem_create.ts b/api/src/workflowitem_create.ts index 8c60abe88..0f9bc1160 100644 --- a/api/src/workflowitem_create.ts +++ b/api/src/workflowitem_create.ts @@ -195,6 +195,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to create workflowitem")); reply.status(code).send(body); + request.log.error({ err: bodyResult }, "Invalid request body"); return; } @@ -235,6 +236,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi .catch((err) => { const { code, body } = toHttpError(err); reply.status(code).send(body); + request.log.error({ err }, "Error while creating Workflowitem"); }); }, ); diff --git a/api/src/workflowitem_download_document.ts b/api/src/workflowitem_download_document.ts index 14c8a2303..7b6153f1b 100644 --- a/api/src/workflowitem_download_document.ts +++ b/api/src/workflowitem_download_document.ts @@ -66,18 +66,20 @@ interface Service { ): Promise>; } -function sendErrorIfEmpty(reply, resourceParameter) { +function sendErrorIfEmpty(reply, resourceParameter): string | undefined { if (!isNonemptyString(resourceParameter)) { + const message = `required query parameter ${resourceParameter} not present (must be non-empty string)`; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: `required query parameter ${resourceParameter} not present (must be non-empty string)`, + message, }, }); - return true; + + return message; } - return false; + return; } interface Request extends RequestGenericInterface { @@ -103,13 +105,14 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }; const { projectId, subprojectId, workflowitemId, documentId } = request.query; - - if ( + const message = sendErrorIfEmpty(reply, projectId) || sendErrorIfEmpty(reply, subprojectId) || sendErrorIfEmpty(reply, workflowitemId) || - sendErrorIfEmpty(reply, documentId) - ) { + sendErrorIfEmpty(reply, documentId); + + if (message) { + request.log.error({ err: message }, "Invalid request body"); return; } @@ -136,6 +139,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(Buffer.from(documentResult.base64, "base64")); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while downloading workflowitem document"); reply.status(code).send(body); } }, diff --git a/api/src/workflowitem_list.ts b/api/src/workflowitem_list.ts index 361453b50..37ebd03b5 100644 --- a/api/src/workflowitem_list.ts +++ b/api/src/workflowitem_list.ts @@ -139,18 +139,19 @@ interface Service { ): Promise>; } -function sendErrorIfEmpty(reply, resourceId) { +function sendErrorIfEmpty(reply, resourceId): string | undefined { if (!isNonemptyString(resourceId)) { + const message = `required query parameter ${resourceId} not present (must be non-empty string)`; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: `required query parameter ${resourceId} not present (must be non-empty string)`, + message, }, }); - return true; + return message; } - return false; + return; } interface Request extends RequestGenericInterface { @@ -175,7 +176,9 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; const subprojectId = request.query.subprojectId; - if (sendErrorIfEmpty(reply, projectId) || sendErrorIfEmpty(reply, subprojectId)) { + const message = sendErrorIfEmpty(reply, projectId) || sendErrorIfEmpty(reply, subprojectId); + if (message) { + request.log.error({ err: message }, "Invalid request body"); return; } @@ -187,6 +190,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi } const workflowitems = workflowitemsResult; + request.log.debug("Mapping workflowitmes to exposedworkflowitems"); return workflowitems.map((workflowitem) => { const exposedWorkflowitem: ExposedWorkflowitem = { allowedIntents: workflowitem.isRedacted @@ -226,6 +230,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing workflowitems"); reply.status(code).send(body); }); }, diff --git a/api/src/workflowitem_permission_grant.ts b/api/src/workflowitem_permission_grant.ts index 76894144a..aebabc801 100644 --- a/api/src/workflowitem_permission_grant.ts +++ b/api/src/workflowitem_permission_grant.ts @@ -123,6 +123,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to grant workflowitem permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -158,6 +159,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while granting workflowitem permission"); reply.status(code).send(body); }); }, diff --git a/api/src/workflowitem_permission_revoke.ts b/api/src/workflowitem_permission_revoke.ts index 11a40200c..eab90975d 100644 --- a/api/src/workflowitem_permission_revoke.ts +++ b/api/src/workflowitem_permission_revoke.ts @@ -124,6 +124,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to revoke workflowitem permission"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -159,6 +160,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while revoking workflowitem permission"); reply.status(code).send(body); }); }, diff --git a/api/src/workflowitem_permissions_list.ts b/api/src/workflowitem_permissions_list.ts index 03e999f1e..b9337258e 100644 --- a/api/src/workflowitem_permissions_list.ts +++ b/api/src/workflowitem_permissions_list.ts @@ -69,18 +69,19 @@ interface Service { ): Promise>; } -function sendErrorIfEmpty(reply, resourceId) { +function sendErrorIfEmpty(reply, resourceId): string | undefined { if (!isNonemptyString(resourceId)) { + const message = `required query parameter ${resourceId} not present (must be non-empty string)`; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: `required query parameter ${resourceId} not present (must be non-empty string)`, + message, }, }); - return true; + return message; } - return false; + return; } interface Request extends RequestGenericInterface { @@ -105,12 +106,13 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }; const { projectId, subprojectId, workflowitemId } = request.query; - - if ( + const message = sendErrorIfEmpty(reply, projectId) || sendErrorIfEmpty(reply, subprojectId) || - sendErrorIfEmpty(reply, workflowitemId) - ) { + sendErrorIfEmpty(reply, workflowitemId); + + if (message) { + request.log.error({ err: message }, "Invalid request body"); return; } try { @@ -137,6 +139,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while listing workflowitem permissions"); reply.status(code).send(body); } }, diff --git a/api/src/workflowitem_update.ts b/api/src/workflowitem_update.ts index ad3363c7b..5c355e92e 100644 --- a/api/src/workflowitem_update.ts +++ b/api/src/workflowitem_update.ts @@ -151,6 +151,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "failed to update project")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -172,6 +173,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while updating workflowitem"); reply.status(code).send(body); }); }); diff --git a/api/src/workflowitem_validate_document.ts b/api/src/workflowitem_validate_document.ts index 29a7f9788..196808436 100644 --- a/api/src/workflowitem_validate_document.ts +++ b/api/src/workflowitem_validate_document.ts @@ -131,6 +131,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const bodyResult = validateRequestBody(request.body); if (Result.isErr(bodyResult)) { const { code, body } = toHttpError(new VError(bodyResult, "invalid request")); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -177,6 +178,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while validating workflowitem document"); reply.status(code).send(body); }); }, diff --git a/api/src/workflowitem_view_details.ts b/api/src/workflowitem_view_details.ts index 1e66f1b8a..65e2e4679 100644 --- a/api/src/workflowitem_view_details.ts +++ b/api/src/workflowitem_view_details.ts @@ -146,18 +146,19 @@ interface Request extends RequestGenericInterface { }; } -function sendErrorIfEmpty(reply, resourceId) { +function sendErrorIfEmpty(reply, resourceId): string | undefined { if (!isNonemptyString(resourceId)) { + const message = `required query parameter ${resourceId} not present (must be non-empty string)`; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: `required query parameter ${resourceId} not present (must be non-empty string)`, + message, }, }); - return true; + return message; } - return false; + return; } export function addHttpHandler(server: FastifyInstance, urlPrefix: string, service: Service) { @@ -177,11 +178,13 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const subprojectId = request.query.subprojectId; const workflowitemId = request.query.workflowitemId; - if ( + const message = sendErrorIfEmpty(reply, projectId) || sendErrorIfEmpty(reply, subprojectId) || - sendErrorIfEmpty(reply, workflowitemId) - ) { + sendErrorIfEmpty(reply, workflowitemId); + + if (message) { + request.log.error({ err: message }, "Invalid request body"); return; } @@ -231,6 +234,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting workflowitem details"); reply.status(code).send(body); }); }, diff --git a/api/src/workflowitem_view_history.ts b/api/src/workflowitem_view_history.ts index 6861a165c..fb8ba6afc 100644 --- a/api/src/workflowitem_view_history.ts +++ b/api/src/workflowitem_view_history.ts @@ -1,4 +1,4 @@ -import { FastifyInstance, FastifyReply, RequestGenericInterface } from "fastify"; +import { FastifyInstance, FastifyReply, RequestGenericInterface, FastifyRequest } from "fastify"; import Joi = require("joi"); import VError = require("verror"); @@ -9,7 +9,6 @@ import { Ctx } from "./lib/ctx"; import { isNonemptyString } from "./lib/validation"; import * as Result from "./result"; import { businessEventSchema } from "./service/domain/business_event"; -import { Identity } from "./service/domain/organization/identity"; import { ServiceUser } from "./service/domain/organization/service_user"; import * as History from "./service/domain/workflow/historyFilter"; import * as Project from "./service/domain/workflow/project"; @@ -37,25 +36,22 @@ function validateRequestBody(body: any): Result.Type { /** * If no filter option is provided the return value is undefined */ -const createFilter = ( - reply: FastifyReply, - publisher?: Identity, - startAt?: string, - endAt?: string, - eventType?: string, -): History.Filter | undefined => { +const createFilter = (reply: FastifyReply, request: FastifyRequest): History.Filter | undefined => { + const { publisher, startAt, endAt, eventType } = request.query as Querystring; const noFilterSet = !publisher && !startAt && !endAt && !eventType; if (noFilterSet) return; if (publisher !== undefined) { if (!isNonemptyString(publisher)) { + const message = "if present, the query parameter `publisher` must be non-empty string"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `publisher` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } @@ -63,38 +59,44 @@ const createFilter = ( if (startAt !== undefined) { const startAtDate = new Date(startAt); if (isNaN(startAtDate.getTime())) { + const message = "if present, the query parameter `startAt` must be a valid ISO timestamp"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `startAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (endAt !== undefined) { const endAtDate = new Date(endAt); if (isNaN(endAtDate.getTime())) { + const message = "if present, the query parameter `endAt` must be a valid ISO timestamp"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `endAt` must be a valid ISO timestamp", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } if (eventType !== undefined) { if (!isNonemptyString(eventType)) { + const message = "if present, the query parameter `eventType` must be non-empty string"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `eventType` must be non-empty string", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); } } return { @@ -214,22 +216,20 @@ interface Service { ): Promise>; } -interface Request extends RequestGenericInterface { - Querystring: { - projectId: string; - subprojectId: string; - workflowitemId: string; - offset?: string; - limit?: string; - startAt?: string; - endAt?: string; - publisher?: string; - eventType?: string; - }; +interface Querystring extends RequestGenericInterface { + projectId: string; + subprojectId: string; + workflowitemId: string; + offset?: string; + limit?: string; + startAt?: string; + endAt?: string; + publisher?: string; + eventType?: string; } export function addHttpHandler(server: FastifyInstance, urlPrefix: string, service: Service) { - server.get( + server.get<{ Querystring: Querystring }>( `${urlPrefix}/workflowitem.viewHistory`, mkSwaggerSchema(server), async (request, reply) => { @@ -243,39 +243,46 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const projectId = request.query.projectId; if (!isNonemptyString(projectId)) { + const message = + "required query parameter `projectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: "required query parameter `projectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const subprojectId = request.query.subprojectId; if (!isNonemptyString(subprojectId)) { + const message = + "required query parameter `subprojectId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `subprojectId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } const workflowitemId = request.query.workflowitemId; if (!isNonemptyString(workflowitemId)) { + const message = + "required query parameter `workflowitemId` not present (must be non-empty string)"; reply.status(404).send({ apiVersion: "1.0", error: { code: 404, - message: - "required query parameter `workflowitemId` not present (must be non-empty string)", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } @@ -284,13 +291,15 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.offset !== undefined) { offset = parseInt(request.query.offset, 10); if (isNaN(offset)) { + const message = "if present, the query parameter `offset` must be an integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `offset` must be an integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } @@ -299,24 +308,20 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi if (request.query.limit !== undefined) { limit = parseInt(request.query.limit, 10); if (isNaN(limit) || limit <= 0) { + const message = "if present, the query parameter `limit` must be a positive integer"; reply.status(400).send({ apiVersion: "1.0", error: { code: 400, - message: "if present, the query parameter `limit` must be a positive integer", + message, }, }); + request.log.error({ err: message }, "Invalid request body"); return; } } - const filter = createFilter( - reply, - request.query.publisher, - request.query.startAt, - request.query.endAt, - request.query.eventType, - ); + const filter = createFilter(reply, request); try { // Get all Events in project stream @@ -354,6 +359,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi reply.status(code).send(body); } catch (err) { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while getting workflowitem history"); reply.status(code).send(body); } }, diff --git a/api/src/workflowitems_reorder.ts b/api/src/workflowitems_reorder.ts index 1da89e8d4..4b3eef6de 100644 --- a/api/src/workflowitems_reorder.ts +++ b/api/src/workflowitems_reorder.ts @@ -119,6 +119,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi const { code, body } = toHttpError( new VError(bodyResult, "failed to reorder workflowitems"), ); + request.log.error({ err: bodyResult }, "Invalid request body"); reply.status(code).send(body); return; } @@ -140,6 +141,7 @@ export function addHttpHandler(server: FastifyInstance, urlPrefix: string, servi }) .catch((err) => { const { code, body } = toHttpError(err); + request.log.error({ err }, "Error while reordering workflowitems"); reply.status(code).send(body); }); }, diff --git a/api/tsconfig.json b/api/tsconfig.json index 54cb497b7..a858813d4 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -9,8 +9,12 @@ "strict": false, "noUnusedLocals": false, "noUnusedParameters": false, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "baseUrl": "src", + "paths": { + "lib/*": ["lib/*"] + } }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "src/**/*.spec.ts"], "exclude": ["node_modules", "coverage"] } From 8de41e51afb4182e4a06da2261e380a35244743e Mon Sep 17 00:00:00 2001 From: Mayr Martin <22145802+mayrmartin@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:06:30 +0100 Subject: [PATCH 2/8] blockchain: - add trace output - print multichain debug messages on stdout --- blockchain/README.md | 2 +- blockchain/package-lock.json | 471 +++++++++++++++++- blockchain/package.json | 2 +- blockchain/src/connectToChain.js | 25 +- blockchain/src/createChain.js | 19 +- blockchain/src/index.js | 4 +- blockchain/src/kubernetesClient.js | 4 +- blockchain/src/log/logArguments.js | 20 + blockchain/src/{ => log}/logger.js | 0 .../notificationWatcher.js | 2 +- blockchain/src/shell.js | 13 +- 11 files changed, 511 insertions(+), 51 deletions(-) create mode 100644 blockchain/src/log/logArguments.js rename blockchain/src/{ => log}/logger.js (100%) diff --git a/blockchain/README.md b/blockchain/README.md index 728f2f9fc..fa28df88b 100644 --- a/blockchain/README.md +++ b/blockchain/README.md @@ -37,7 +37,7 @@ Depending on the Trubudget setup environment variables | ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain. | | P2P_HOST | no | | The IP address of the blockchain node you want to connect to. When given, the node joins the existing network rather than creating its own chain. | | P2P_PORT | no | 7447 | The port on which the node you want to connect to has exposed the blockchain. | -| PRETTY_PRINT | no | true | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | +| PRETTY_PRINT | no | false | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | | RPC_ALLOW_IP | no | 0.0.0.0/0 | It refers to an allowed IP address range, given either by IP or CIDR notation. 0.0.0.0/0 will allow access from anywhere. | | RPC_USER | no | multichainrpc | The user used to connect to the multichain daemon. | | RPC_PASSWORD | no | [hardcoded] | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every slave node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the RPC_PASSWORD is not required it is highly recommended to set an own secure one | diff --git a/blockchain/package-lock.json b/blockchain/package-lock.json index 44526ece1..d812caa34 100644 --- a/blockchain/package-lock.json +++ b/blockchain/package-lock.json @@ -23,7 +23,7 @@ "sinon": "^7.2.2", "streamifier": "^0.1.1", "tar-fs": "^2.1.0", - "trubudget-logging-service": "^1.1.2" + "trubudget-logging-service": "^1.1.3" }, "devDependencies": { "babel-eslint": "^10.1.0", @@ -421,6 +421,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "dependencies": { + "ajv": "^6.12.6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -690,6 +698,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -817,8 +830,7 @@ "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "node_modules/argparse": { "version": "1.0.10", @@ -944,6 +956,17 @@ "node": ">=8.0.0" } }, + "node_modules/avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1732,7 +1755,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1792,6 +1814,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -2646,6 +2676,11 @@ "node >=0.6.0" ] }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2656,6 +2691,20 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "dependencies": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -2688,11 +2737,74 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, + "node_modules/fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "dependencies": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + } + }, + "node_modules/fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "dependencies": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "node_modules/fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "node_modules/fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "node_modules/fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "node_modules/fastify/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2846,6 +2958,20 @@ "node": ">=8" } }, + "node_modules/find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -4251,6 +4377,37 @@ "node": ">= 0.8.0" } }, + "node_modules/light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "dependencies": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/light-my-request/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/light-my-request/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -4465,7 +4622,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -5754,6 +5910,25 @@ "node": ">=0.6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -5995,7 +6170,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6036,6 +6210,23 @@ "lowercase-keys": "^1.0.0" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -6061,6 +6252,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dependencies": { + "ret": "~0.2.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6092,6 +6291,11 @@ "node": ">=8" } }, + "node_modules/semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -6162,6 +6366,11 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "node_modules/set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -6459,6 +6668,11 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6667,6 +6881,14 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "engines": { + "node": ">=6" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6730,15 +6952,26 @@ } }, "node_modules/trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "dependencies": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" } }, + "node_modules/trubudget-logging-service/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, "node_modules/tsconfig-paths": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", @@ -7243,8 +7476,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yargs": { "version": "16.2.0", @@ -7612,6 +7844,14 @@ } } }, + "@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "requires": { + "ajv": "^6.12.6" + } + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -7841,6 +8081,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -7935,8 +8180,7 @@ "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "argparse": { "version": "1.0.10", @@ -8035,6 +8279,17 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "requires": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -8628,7 +8883,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -8668,6 +8922,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "default-require-extensions": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", @@ -9346,6 +9605,11 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, + "fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9356,6 +9620,17 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "requires": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + } + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -9387,11 +9662,70 @@ } } }, + "fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "requires": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "requires": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9510,6 +9844,17 @@ } } }, + "find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "requires": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + } + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -10518,6 +10863,35 @@ "type-check": "~0.4.0" } }, + "light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "requires": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -10694,7 +11068,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -11660,6 +12033,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -11852,8 +12230,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require-main-filename": { "version": "2.0.0", @@ -11885,6 +12262,16 @@ "lowercase-keys": "^1.0.0" } }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -11904,6 +12291,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "requires": { + "ret": "~0.2.0" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11929,6 +12324,11 @@ "semver": "^6.3.0" } }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -11997,6 +12397,11 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -12231,6 +12636,11 @@ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12389,6 +12799,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -12434,13 +12849,26 @@ } }, "trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "requires": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" + }, + "dependencies": { + "axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "requires": { + "follow-redirects": "^1.14.4" + } + } } }, "tsconfig-paths": { @@ -12846,8 +13274,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "16.2.0", diff --git a/blockchain/package.json b/blockchain/package.json index e9e587e94..e4eab46a0 100644 --- a/blockchain/package.json +++ b/blockchain/package.json @@ -49,7 +49,7 @@ "sinon": "^7.2.2", "streamifier": "^0.1.1", "tar-fs": "^2.1.0", - "trubudget-logging-service": "^1.1.2" + "trubudget-logging-service": "^1.1.3" }, "devDependencies": { "babel-eslint": "^10.1.0", diff --git a/blockchain/src/connectToChain.js b/blockchain/src/connectToChain.js index bed139081..e87ad7e18 100644 --- a/blockchain/src/connectToChain.js +++ b/blockchain/src/connectToChain.js @@ -1,8 +1,11 @@ const axios = require("axios"); const spawn = require("child_process").spawn; const fs = require("fs"); -const log = require("./logger"); - +const log = require("./log/logger"); +const mdLog = require("trubudget-logging-service").createPinoLogger( + "Multichain-Slave", +); +const includeLoggingParamsToArgs = require("./log/logArguments"); // global: let address; @@ -49,7 +52,8 @@ function startSlave( }, "Start slave nodes with params", ); - const args = [ + + const args = includeLoggingParamsToArgs([ "-txindex", `-port=${p2pPort}`, "-autosubscribe=streams", @@ -57,7 +61,7 @@ function startSlave( connectArgs, blockNotifyArg, externalIpArg, - ]; + ]); log.info(args, "Chain Arguments: "); if (fs.existsSync(chainConfigPath)) { @@ -82,10 +86,12 @@ function startSlave( } } + log.debug({ args }, "Starting multichain deamon with arguments"); + const mc = spawn(prog, args); mc.stdout.on("data", (data) => { - log.info(`${prog} | ${data.toString()}`); + mdLog.info(`${data}`); const regex = new RegExp("[0-9a-zA-Z]{30,40}"); const match = regex.exec(data); if (match) address = match[0]; @@ -94,7 +100,7 @@ function startSlave( mc.stderr.on("data", (err) => log.error({ err }, "Error in Slave")); mc.on("close", (code, signal) => - log.info( + mdLog.warn( `Multichaind (slave node) closed with exit code ${code} and signal ${signal}.`, ), ); @@ -120,18 +126,21 @@ async function registerNodeAtMaster(organization, proto, host, port) { while (!address) { await relax(5000); } + log.info(`Registering ${organization} node address ${address}`); await askMasterForPermissions(address, organization, proto, host, port); log.info("Node address registered successfully (approval pending)."); } catch (error) { log.error( - `Could not register (${error}). Retry in ${retryIntervalMs / - 1000} seconds ...`, + `Could not register (${error}). Retry in ${ + retryIntervalMs / 1000 + } seconds ...`, ); await relax(retryIntervalMs); await registerNodeAtMaster(organization, proto, host, port); } } + module.exports = { startSlave, registerNodeAtMaster, diff --git a/blockchain/src/createChain.js b/blockchain/src/createChain.js index e4e1665ac..fd00b0401 100644 --- a/blockchain/src/createChain.js +++ b/blockchain/src/createChain.js @@ -1,9 +1,10 @@ const spawn = require("child_process").spawn; const shell = require("shelljs"); -const log = require("./logger"); +const log = require("./log/logger"); const mdLog = require("trubudget-logging-service").createPinoLogger( "Multichain-Deamon", ); +const includeLoggingParamsToArgs = require("./log/logArguments"); const configureChain = ( isMaster, @@ -21,10 +22,7 @@ const configureChain = ( if (isMaster) { log.info("Provisioning MultiChain"); - const { - stdout, - stderr, - } = shell.exec( + const { stdout, stderr } = shell.exec( `multichain-util create ${chainName} -datadir=${multichainDir} -anyone-can-connect=false -anyone-can-send=false -anyone-can-receive=true -anyone-can-receive-empty=true -anyone-can-create=false -anyone-can-issue=false -anyone-can-admin=false -anyone-can-mine=false -anyone-can-activate=false-mining-diversity=0.3 -mine-empty-rounds=1 -protocol-version=20005 -admin-consensus-upgrade=.51 -admin-consensus-admin=.51 -admin-consensus-activate=.51 -admin-consensus-mine=.51 -admin-consensus-create=0 -admin-consensus-issue=0 -root-stream-open=false -maximum-block-size=83886080`, { silent: true }, ); @@ -66,7 +64,7 @@ const startMultichainDaemon = ( multichainDir, connectArg = "", ) => { - const mcproc = spawn("multichaind", [ + const args = includeLoggingParamsToArgs([ "-txindex", `${chainName}`, `${externalIpArg}`, @@ -76,12 +74,15 @@ const startMultichainDaemon = ( "-autosubscribe=streams", `${connectArg}`, `-datadir=${multichainDir}`, - "-debug=mcapi", ]); - mcproc.stdout.on("data", data => { + log.debug({ args }, "Starting multichain deamon with arguments"); + const mcproc = spawn("multichaind", args); + + mcproc.stdout.on("data", (data) => { mdLog.info(`${data}`); }); - mcproc.stderr.on("data", data => { + + mcproc.stderr.on("data", (data) => { if (data.includes("multichain-feed")) { mdLog.info({ feed: data }, "multichain-feed "); } else { diff --git a/blockchain/src/index.js b/blockchain/src/index.js index 834981f99..89158afd3 100644 --- a/blockchain/src/index.js +++ b/blockchain/src/index.js @@ -8,7 +8,7 @@ const yaml = require("js-yaml"); const k8s = require("@kubernetes/client-node"); const os = require("os"); const KubernetesClient = require("./kubernetesClient"); -const log = require("./logger"); +const log = require("./log/logger"); const logService = require("trubudget-logging-service"); const { @@ -75,7 +75,7 @@ const NAMESPACE = process.env.KUBE_NAMESPACE || ""; const EXPOSE_MC = process.env.EXPOSE_MC === "true" ? true : false; if (EMAIL_SERVICE_ENABLED && !emailAuthSecret) { - log.error( + log.fatal( "Env variable 'JWT_SECRET' not set. Please set the same secret as in the trubudget email-service.", ); os.exit(1); diff --git a/blockchain/src/kubernetesClient.js b/blockchain/src/kubernetesClient.js index f16df4737..c3a4dde0d 100644 --- a/blockchain/src/kubernetesClient.js +++ b/blockchain/src/kubernetesClient.js @@ -1,7 +1,7 @@ -const log = require("./logger"); +const log = require("./log/logger"); function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } class KubernetesClient { constructor(k8sApi) { diff --git a/blockchain/src/log/logArguments.js b/blockchain/src/log/logArguments.js new file mode 100644 index 000000000..89ad4e10b --- /dev/null +++ b/blockchain/src/log/logArguments.js @@ -0,0 +1,20 @@ +const multiChainDebugParameter = + "-debug:mcapi,mchn, mccoin, mcatxo, mcminer, mcblock"; + +const printToConsole = "-printtoconsole"; + +const includeLoggingParamsToArgs = (args) => { + const logLevel = (process.env.LOG_LEVEL || "").toLowerCase(); + + if (logLevel !== "debug" && logLevel !== "trace") { + return [...args]; + } + + if (args.includes(printToConsole)) { + return [...args, multiChainDebugParameter]; + } + + return [...args, multiChainDebugParameter, printToConsole]; +}; + +module.exports = includeLoggingParamsToArgs; diff --git a/blockchain/src/logger.js b/blockchain/src/log/logger.js similarity index 100% rename from blockchain/src/logger.js rename to blockchain/src/log/logger.js diff --git a/blockchain/src/multichain-feed/email-notifications/notificationWatcher.js b/blockchain/src/multichain-feed/email-notifications/notificationWatcher.js index 3fca27bcc..e7dcc092c 100644 --- a/blockchain/src/multichain-feed/email-notifications/notificationWatcher.js +++ b/blockchain/src/multichain-feed/email-notifications/notificationWatcher.js @@ -1,7 +1,7 @@ const fork = require("child_process").fork; const fs = require("fs"); -const log = require("./../../logger"); +const log = require("./../../log/logger"); const startEmailNotificationWatcher = ( path, diff --git a/blockchain/src/shell.js b/blockchain/src/shell.js index 8c905f05a..3956d5044 100644 --- a/blockchain/src/shell.js +++ b/blockchain/src/shell.js @@ -3,7 +3,8 @@ const fs = require("fs"); const { md5Dir } = require("./md5"); const { sha256Dir } = require("./sha256"); -const log = require("./logger"); + +const logger = require("./log/logger"); const verifyHash = async (backupDirectoryHash, extractPath) => (await md5Dir(extractPath)) === backupDirectoryHash; @@ -17,12 +18,12 @@ const createMetadataFileSha256 = async ( try { dirHash = await sha256Dir(`${multichainDir}/${chainName}`); } catch (err) { - log.error({ err }, "sha256 error"); + logger.error({ err }, "sha256 error"); } const filePath = `${multichainDir}/${chainName}/metadata.yml`; shell.touch(filePath); - log.info("Backup Metadata SHA256"); + logger.info("Backup Metadata SHA256"); const ts = Date.now(); shell .echo( @@ -37,18 +38,20 @@ const verifyHashSha256 = async (backupDirectoryHash, extractPath) => { return dirHash === backupDirectoryHash; }; -const createCurrentChainBackupDir = currentChainBackupDir => { +const createCurrentChainBackupDir = (currentChainBackupDir) => { + logger.trace({ dir: currentChainBackupDir }, "Creating backup directory"); if (fs.existsSync(currentChainBackupDir)) { shell.rm("-rf", currentChainBackupDir); } shell.mkdir(currentChainBackupDir); }; -const removeFile = path => { +const removeFile = (path) => { shell.rm(path); }; const moveBackup = async (multichainDir, extractPath, chainName) => { + logger.debug("Moving backup"); const targetDir = `${multichainDir}/${chainName}`; const currentChainBackupDir = "/root/bcBackup"; createCurrentChainBackupDir(currentChainBackupDir); From 213fbd27839812380e1983c1cf91840375d40996 Mon Sep 17 00:00:00 2001 From: Mayr Martin <22145802+mayrmartin@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:11:52 +0100 Subject: [PATCH 3/8] email-notification: - add trace messages to logging --- email-notification-service/package-lock.json | 629 ++++++++++++++++++- email-notification-service/package.json | 2 +- email-notification-service/src/db.ts | 8 + email-notification-service/src/index.ts | 74 ++- email-notification-service/src/middleware.ts | 4 + email-notification-service/src/sendMail.ts | 6 +- email-notification-service/src/validation.ts | 1 + 7 files changed, 672 insertions(+), 52 deletions(-) diff --git a/email-notification-service/package-lock.json b/email-notification-service/package-lock.json index b58f44ed3..1dd2fe276 100644 --- a/email-notification-service/package-lock.json +++ b/email-notification-service/package-lock.json @@ -19,7 +19,7 @@ "knex": "^0.95.11", "nodemailer": "^6.4.2", "pg": "^8.6.0", - "trubudget-logging-service": "^1.1.2" + "trubudget-logging-service": "^1.1.3" }, "devDependencies": { "@types/express": "^4.17.2", @@ -30,6 +30,14 @@ "typescript": "^4.0.2" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "dependencies": { + "ajv": "^6.12.6" + } + }, "node_modules/@hapi/address": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", @@ -212,6 +220,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -224,6 +237,21 @@ "node": ">= 0.6" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -266,6 +294,11 @@ "node": ">= 8" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, "node_modules/args": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", @@ -293,6 +326,38 @@ "node": ">=8.0.0" } }, + "node_modules/avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + } + }, + "node_modules/avvio/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/avvio/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -766,6 +831,14 @@ "node": ">=4.0.0" } }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -936,6 +1009,35 @@ "node": ">= 8.0.0" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "dependencies": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fast-redact": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.2.tgz", @@ -957,11 +1059,74 @@ "punycode": "^1.3.2" } }, + "node_modules/fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "dependencies": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + } + }, + "node_modules/fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "dependencies": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "node_modules/fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "node_modules/fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "node_modules/fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "node_modules/fastify/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -979,15 +1144,29 @@ "node": ">= 0.8" } }, + "node_modules/find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" }, "node_modules/follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", "funding": [ { "type": "individual", @@ -1361,6 +1540,11 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -1499,6 +1683,37 @@ "node": ">=0.10.0" } }, + "node_modules/light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "dependencies": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/light-my-request/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/light-my-request/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -1552,7 +1767,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -2124,6 +2338,25 @@ "node": ">=0.6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -2232,6 +2465,14 @@ "node": ">=8" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -2261,6 +2502,23 @@ "lowercase-keys": "^1.0.0" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -2271,6 +2529,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dependencies": { + "ret": "~0.2.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2310,6 +2576,11 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -2352,6 +2623,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -2415,6 +2691,11 @@ } ] }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2477,6 +2758,14 @@ "node": ">=8" } }, + "node_modules/tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "engines": { + "node": ">=6" + } + }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -2507,15 +2796,26 @@ } }, "node_modules/trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "dependencies": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" } }, + "node_modules/trubudget-logging-service/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -2704,6 +3004,22 @@ "node": ">=8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -2730,9 +3046,9 @@ } }, "node_modules/validator": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", - "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", "engines": { "node": ">= 0.10" } @@ -2844,11 +3160,18 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } }, "dependencies": { + "@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "requires": { + "ajv": "^6.12.6" + } + }, "@hapi/address": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", @@ -3021,6 +3344,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -3030,6 +3358,17 @@ "negotiator": "0.6.2" } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -3063,6 +3402,11 @@ "picomatch": "^2.0.4" } }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, "args": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", @@ -3084,6 +3428,32 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "requires": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -3447,6 +3817,11 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -3587,6 +3962,32 @@ "validator": "^13.5.2" } }, + "fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "requires": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + } + }, "fast-redact": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.2.tgz", @@ -3605,11 +4006,70 @@ "punycode": "^1.3.2" } }, + "fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "requires": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "requires": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -3624,15 +4084,26 @@ "unpipe": "~1.0.0" } }, + "find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "requires": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + } + }, "flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" }, "follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" }, "forwarded": { "version": "0.2.0", @@ -3895,6 +4366,11 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "jsonwebtoken": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", @@ -3996,6 +4472,35 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" }, + "light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "requires": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -4046,7 +4551,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -4481,6 +4985,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -4567,6 +5076,11 @@ "rc": "^1.2.8" } }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -4590,6 +5104,16 @@ "lowercase-keys": "^1.0.0" } }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -4600,6 +5124,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "requires": { + "ret": "~0.2.0" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4632,6 +5164,11 @@ } } }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -4670,6 +5207,11 @@ "send": "0.17.1" } }, + "set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -4718,6 +5260,11 @@ } } }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4762,6 +5309,11 @@ "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==" }, + "tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -4783,13 +5335,26 @@ } }, "trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "requires": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" + }, + "dependencies": { + "axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "requires": { + "follow-redirects": "^1.14.4" + } + } } }, "type-fest": { @@ -4927,6 +5492,21 @@ } } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -4947,9 +5527,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "validator": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", - "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" }, "vary": { "version": "1.1.2", @@ -5033,8 +5613,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/email-notification-service/package.json b/email-notification-service/package.json index 82599792f..b1e51a084 100644 --- a/email-notification-service/package.json +++ b/email-notification-service/package.json @@ -38,7 +38,7 @@ "knex": "^0.95.11", "nodemailer": "^6.4.2", "pg": "^8.6.0", - "trubudget-logging-service": "^1.1.2" + "trubudget-logging-service": "^1.1.3" }, "devDependencies": { "@types/express": "^4.17.2", diff --git a/email-notification-service/src/db.ts b/email-notification-service/src/db.ts index f49487fc4..1b61055ec 100644 --- a/email-notification-service/src/db.ts +++ b/email-notification-service/src/db.ts @@ -24,9 +24,11 @@ class DbConnector { public getDb = async (): Promise => { if (!this.pool) { + logger.trace("Initializing DB connection(s) ..."); this.pool = this.initializeConnection(); } if (!(await this.pool.schema.hasTable(config.userTable))) { + logger.trace("No tables found - creating them now!"); await this.createTable(); } return this.pool; @@ -35,14 +37,17 @@ class DbConnector { public disconnect = async () => { if (this.pool) { await this.pool.destroy(); + logger.trace("Disconnected form DB"); } }; public healthCheck = async (): Promise => { + logger.debug("Starting health check"); const client = await this.getDb(); const tablesToCheck: string[] = [config.userTable]; const tablePromises: Promise = Promise.all( tablesToCheck.map((table) => { + logger.trace({ table }, "Checking table"); const query: Knex.QueryBuilder = client.select().from(table).whereRaw("1=0"); return this.executeQuery(query, `The table ${table} does not exist.`); }), @@ -100,6 +105,7 @@ class DbConnector { }; public getAllEmails = async (): Promise => { + logger.trace("Getting all emails"); const client = await this.getDb(); return (await client(config.userTable).select(this.emailAddressTableName)).reduce( (emailAddresses: string[], emailAddress: EmailAddress) => { @@ -113,6 +119,7 @@ class DbConnector { public getEmailAddress = async (id: string): Promise => { try { const client = await this.getDb(); + logger.trace({ id }, "Getting email address from user by id"); const emailAddresses: EmailAddress[] = await client(config.userTable) .select(this.emailAddressTableName) .where({ [`${this.idTableName}`]: `${id}` }); @@ -141,6 +148,7 @@ class DbConnector { }; private createTable = async (): Promise => { + logger.debug("Creating user table"); await this.pool.schema.createTable(config.userTable, (table) => { table.string(this.idTableName).notNullable().unique(); table.string(this.emailAddressTableName).notNullable(); diff --git a/email-notification-service/src/index.ts b/email-notification-service/src/index.ts index 9ec950abb..96df55602 100644 --- a/email-notification-service/src/index.ts +++ b/email-notification-service/src/index.ts @@ -1,21 +1,21 @@ -import express from "express"; import cors from "cors"; +import express from "express"; import { body, query } from "express-validator"; import { createPinoExpressLogger } from "trubudget-logging-service"; import config from "./config"; import DbConnector from "./db"; +import logger from "./logger"; import * as Middleware from "./middleware"; import sendMail from "./sendMail"; import { - UserEditRequest, + NotificationRequest, + NotificationResponseBody, User, + UserEditRequest, UserEditResponseBody, UserGetEmailAddressRequest, - NotificationRequest, - NotificationResponseBody, } from "./types"; import isBodyValid from "./validation"; -import logger from "./logger"; // Setup const db = new DbConnector(); @@ -48,8 +48,11 @@ emailService.post( "/user.insert", body("data.user.id").escape(), (req: UserEditRequest, res: express.Response) => { + logger.trace("Validating data"); + const isDataValid = isBodyValid("/user.insert", req.body.data); if (!isDataValid) { + logger.error("Validation error. Data not valid!"); res.status(400).send({ message: `The request body validation failed`, }); @@ -57,26 +60,31 @@ emailService.post( } const user: User = req.body.data.user; if (!isAllowed(user.id, res)) { + const message = `JWT-Token is not valid to insert an email address of user ${user.id}`; + logger.error(message); res.status(401).send({ - message: `JWT-Token is not valid to insert an email address of user ${user.id}`, + message, }); return; } (async () => { + logger.trace({ user }, "Fetching email address for user"); const emailAddress: string = await db.getEmailAddress(user.id); if (emailAddress.length > 0) { + logger.error({ user }, "User already exists"); res.status(400).send({ user: { id: user.id, status: "already exists", emailAddress: user.emailAddress }, }); } else { + logger.trace("Inserted user"); await db.insertUser(user.id, user.emailAddress); res.status(200).send({ user: { id: user.id, status: "inserted", emailAddress: user.emailAddress }, }); } })().catch((error) => { - logger.error(error); + logger.error({ err: error }, "Error while inserting user"); res.status(500).send(error); }); }, @@ -88,35 +96,41 @@ emailService.post( (req: UserEditRequest, res: express.Response) => { const isDataValid = isBodyValid("/user.update", req.body.data); if (!isDataValid) { + logger.error("Validation error. Data not valid!"); res.status(400).send({ - message: `The request body validation failed`, + message: "The request body validation failed", }); return; } const user: User = req.body.data.user; if (!isAllowed(user.id, res)) { + const message = `JWT-Token is not valid to insert an email address of user ${user.id}`; + logger.error(message); res.status(401).send({ - message: `JWT-Token is not valid to insert an email address of user ${user.id}`, + message, }); return; } (async () => { const emailAddress: string = await db.getEmailAddress(user.id); + logger.trace("Fetching email address for user: ", user.id); let body: UserEditResponseBody; if (emailAddress.length > 0) { body = { user: { id: user.id, status: "updated", emailAddress: user.emailAddress } }; + logger.trace("Updateing user ", user.id, user.emailAddress); await db.updateUser(user.id, user.emailAddress); res.status(200); } else { body = { user: { id: user.id, emailAddress: user.emailAddress, status: "not found" }, }; + logger.error({ error: body }, "User not found"); res.status(404); } res.send(body); })().catch((error) => { - logger.error(error); + logger.error({ err: error }, "Error while updating user"); res.status(500).send(error); }); }, @@ -127,7 +141,10 @@ emailService.post( body("data.user.id").escape(), (req: UserEditRequest, res: express.Response) => { const isDataValid = isBodyValid("/user.delete", req.body.data); + logger.trace("Validating data"); + if (!isDataValid) { + logger.error("Validation error. Data not valid!"); res.status(400).send({ message: `The request body validation failed`, }); @@ -135,6 +152,7 @@ emailService.post( } const user: User = req.body.data.user; if (!isAllowed(user.id, res)) { + logger.error(`JWT-Token is not valid to insert an email address of user ${user.id}`); res.status(401).send({ message: `JWT-Token is not valid to insert an email address of user ${user.id}`, }); @@ -146,9 +164,10 @@ emailService.post( const body: UserEditResponseBody = { user: { id: user.id, status: "deleted", emailAddress: user.emailAddress }, }; + logger.trace("Deleted user", user.id, user.emailAddress); res.status(200).send(body); })().catch((error) => { - logger.error(error); + logger.error({ err: error }, "Error while deleting user"); res.status(500).send(error); }); }, @@ -159,15 +178,20 @@ emailService.get( query("id").escape(), (req: UserGetEmailAddressRequest, res: express.Response) => { const isDataValid = isBodyValid("/user.getEmailAddress", req.query); + logger.trace("Validating data"); + if (!isDataValid) { + logger.error("Validation error. Data not valid!"); res.status(400).send({ message: `The request body validation failed`, }); return; } if (!isAllowed(req.query.id, res)) { + const message = `JWT-Token is not valid to insert an email address of user ${req.query.id}`; + logger.error(message); res.status(401).send({ - message: `JWT-Token is not valid to insert an email address of user ${req.query.id}`, + message, }); return; } @@ -176,18 +200,18 @@ emailService.get( (async () => { const emailAddress = await db.getEmailAddress(id); if (emailAddress.length > 0) { - logger.debug("GET email address " + emailAddress + " for user " + id); + logger.trace("GET email address " + emailAddress + " for user " + id); res.send({ user: { id, emailAddress }, }); } else { - logger.debug("Email address" + emailAddress + " not found"); + logger.trace("Email address" + emailAddress + " not found"); res.status(404).send({ user: { id, emailAddress: "Not Found" }, }); } })().catch((error) => { - logger.error(error); + logger.error({ err: error }, "Error while getting email adress"); res.status(500).send(error); }); }, @@ -197,8 +221,10 @@ emailService.post( "/notification.send", body("data.user.id").escape(), (req: NotificationRequest, res: express.Response) => { + logger.trace("Validating data"); const isDataValid = isBodyValid("/notification.send", req.body.data); if (!isDataValid) { + logger.error("Validation error. Data not valid!"); res.status(400).send({ message: `The request body validation failed`, }); @@ -209,8 +235,10 @@ emailService.post( if (config.authentication !== "none") { // Only the notification watcher of the Trubudget blockchain may send notifications if (res.locals.id !== "notification-watcher") { + const message = `${res.locals.id} is not allowed to send a notification to ${id}`; + logger.error(message); res.status(401).send({ - message: `${res.locals.id} is not allowed to send a notification to ${id}`, + message, }); return; } @@ -222,18 +250,18 @@ emailService.post( let body: NotificationResponseBody; if (emailAddress.length > 0) { await sendMail(emailAddress); - logger.debug("Notification sent to " + emailAddress); + logger.trace("Notification sent to " + emailAddress); body = { notification: { recipient: id, status: "sent", emailAddress }, }; res.status(200).send(body); } else { - logger.debug("Email address" + emailAddress + "not found"); + logger.trace("Email address" + emailAddress + "not found"); body = { notification: { recipient: id, status: "deleted", emailAddress: "Not Found" } }; res.status(404).send(body); } })().catch((error) => () => { - logger.error(error); + logger.error({ err: error }, "Error while send notification"); res.status(500).send(error); }); }, @@ -249,11 +277,9 @@ function isAllowed(requestedUserId: string, res: express.Response): boolean { } const requestor: string = res.locals.userId; const allowed: boolean = requestor === "root" || requestor === requestedUserId; - if (!allowed) { - logger.debug( - "Requestor '" + requestor + "' and passed userId '" + requestedUserId + "' are not equal", - ); - } + + logger.debug({ requestor, allowed }, "Checking if requestor is allowed"); + return allowed; } diff --git a/email-notification-service/src/middleware.ts b/email-notification-service/src/middleware.ts index cfd8d038e..5e9cc41ae 100644 --- a/email-notification-service/src/middleware.ts +++ b/email-notification-service/src/middleware.ts @@ -22,6 +22,7 @@ interface AuthNotificationToken { } export const verifyUserJWT = (req: Request, res, next, secret: string) => { + logger.trace("Verifying User-JWT ..."); const token: string = getJWTToken(req); verifyJWTToken(token, secret) @@ -30,11 +31,13 @@ export const verifyUserJWT = (req: Request, res, next, secret: string) => { next(); }) .catch((err) => { + logger.trace({ err, token }, "User-JWT invalid"); const body: InvalidJWTResponseBody = { message: "Invalid JWT token provided." }; res.status(400).json(body); }); }; export const verifyNotificationJWT = (req: Request, res, next, secret: string) => { + logger.trace("Verifying Notification-JWT ..."); const token: string = getJWTToken(req); verifyJWTToken(token, secret) @@ -43,6 +46,7 @@ export const verifyNotificationJWT = (req: Request, res, next, secret: string) = next(); }) .catch((err) => { + logger.trace({ err, token }, "Notification-JWT invalid"); const body: InvalidJWTResponseBody = { message: "Invalid JWT token provided." }; res.status(400).json(body); }); diff --git a/email-notification-service/src/sendMail.ts b/email-notification-service/src/sendMail.ts index 5a92d85d5..0a2bfe717 100644 --- a/email-notification-service/src/sendMail.ts +++ b/email-notification-service/src/sendMail.ts @@ -3,7 +3,7 @@ import config from "./config"; import logger from "./logger"; const sendMail = async (emailAddresses: string | string[]) => { - const transporter = nodemailer.createTransport({ + const transportOptions = { host: config.smtpServer.host, port: config.smtpServer.port, secure: config.smtpServer.secure, @@ -11,7 +11,9 @@ const sendMail = async (emailAddresses: string | string[]) => { user: config.smtpServer.user, pass: config.smtpServer.password, }, - }); + }; + logger.trace({ transportOptions }, "Sending email with transport options"); + const transporter = nodemailer.createTransport(transportOptions); const info: SentMessageInfo = await transporter.sendMail({ from: config.email.from, diff --git a/email-notification-service/src/validation.ts b/email-notification-service/src/validation.ts index 73b310f0d..6abc7f634 100644 --- a/email-notification-service/src/validation.ts +++ b/email-notification-service/src/validation.ts @@ -46,6 +46,7 @@ schemes ); const isBodyValid = (request, payload) => { + logger.debug({ request, payload }, "Checking request validity"); const schema = schemes.get(request); if (!schema) { throw new Error(`Validation schema for request ${request} not implemented yet`); From 8997c0722a8de50403e7dd23fc52d1f7e751c3c3 Mon Sep 17 00:00:00 2001 From: Mayr Martin <22145802+mayrmartin@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:17:09 +0100 Subject: [PATCH 4/8] excel-export: - add trace messages for logging --- excel-export-service/package-lock.json | 678 ++++++++++++++++++++++++- excel-export-service/package.json | 2 +- excel-export-service/src/api.ts | 10 + excel-export-service/src/excel.ts | 22 +- excel-export-service/src/helper.ts | 7 + excel-export-service/src/index.ts | 31 +- excel-export-service/src/logger.ts | 5 + 7 files changed, 722 insertions(+), 33 deletions(-) create mode 100644 excel-export-service/src/logger.ts diff --git a/excel-export-service/package-lock.json b/excel-export-service/package-lock.json index d5600c0f3..45b44168f 100644 --- a/excel-export-service/package-lock.json +++ b/excel-export-service/package-lock.json @@ -16,7 +16,7 @@ "express": "^4.17.1", "jwt-decode": "^2.2.0", "localized-strings": "^0.2.4", - "trubudget-logging-service": "^1.1.2", + "trubudget-logging-service": "^1.1.3", "url": "^0.11.0" }, "devDependencies": { @@ -26,6 +26,14 @@ "typescript": "^4.0.3" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "dependencies": { + "ajv": "^6.12.6" + } + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -93,6 +101,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -105,6 +118,21 @@ "node": ">= 0.6" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -133,6 +161,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/anymatch": { @@ -199,6 +230,11 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, "node_modules/args": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", @@ -276,6 +312,38 @@ "node": ">=8.0.0" } }, + "node_modules/avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + } + }, + "node_modules/avvio/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/avvio/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -309,6 +377,9 @@ "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" } }, "node_modules/binary-extensions": { @@ -372,6 +443,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -464,6 +538,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cacheable-request/node_modules/lowercase-keys": { @@ -490,6 +567,9 @@ "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", "dependencies": { "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" } }, "node_modules/chalk": { @@ -560,6 +640,9 @@ "dev": true, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/clone-response": { @@ -764,6 +847,14 @@ "node": ">=4.0.0" } }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -972,6 +1063,35 @@ "lodash.uniq": "^4.5.0" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "dependencies": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fast-redact": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.2.tgz", @@ -993,11 +1113,74 @@ "punycode": "^1.3.2" } }, + "node_modules/fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "dependencies": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + } + }, + "node_modules/fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "dependencies": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "node_modules/fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "node_modules/fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "node_modules/fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "node_modules/fastify/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1027,6 +1210,20 @@ "node": ">= 0.8" } }, + "node_modules/find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", @@ -1081,7 +1278,9 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" @@ -1141,6 +1340,9 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { @@ -1366,6 +1568,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-npm": { @@ -1435,6 +1640,11 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "node_modules/jszip": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", @@ -1527,6 +1737,37 @@ "immediate": "~3.0.5" } }, + "node_modules/light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "dependencies": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/light-my-request/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/light-my-request/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -1611,6 +1852,17 @@ "node": ">=0.10.0" } }, + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1621,6 +1873,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { @@ -1745,6 +2000,7 @@ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz", "integrity": "sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ==", "dev": true, + "hasInstallScript": true, "dependencies": { "chokidar": "^3.2.2", "debug": "^3.2.6", @@ -1762,6 +2018,10 @@ }, "engines": { "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, "node_modules/nodemon/node_modules/debug": { @@ -1904,6 +2164,9 @@ "dev": true, "engines": { "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pino": { @@ -2062,10 +2325,30 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", "engines": { "node": ">=0.4.x" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -2157,6 +2440,14 @@ "node": ">=8" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -2166,6 +2457,23 @@ "lowercase-keys": "^1.0.0" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -2180,6 +2488,9 @@ }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { @@ -2187,6 +2498,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dependencies": { + "ret": "~0.2.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2238,6 +2557,11 @@ "semver": "bin/semver.js" } }, + "node_modules/semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "node_modules/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -2280,6 +2604,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "node_modules/set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -2337,6 +2666,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "node_modules/string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -2420,6 +2754,17 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==", + "engines": { + "node": ">=6" } }, "node_modules/tmp": { @@ -2477,18 +2822,32 @@ "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "engines": { + "node": "*" + } }, "node_modules/trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "dependencies": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" } }, + "node_modules/trubudget-logging-service/node_modules/axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -2614,6 +2973,25 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" } }, "node_modules/url": { @@ -2709,6 +3087,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/zip-stream": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", @@ -2724,6 +3107,14 @@ } }, "dependencies": { + "@fastify/ajv-compiler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz", + "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", + "requires": { + "ajv": "^6.12.6" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2787,6 +3178,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2796,6 +3192,17 @@ "negotiator": "0.6.2" } }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -2877,6 +3284,11 @@ } } }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, "args": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", @@ -2941,6 +3353,32 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "avvio": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.2.tgz", + "integrity": "sha512-XW2CMCmZaCmCCsIaJaLKxAzPwF37fXi1KGxNOvedOpeisLdmxZnblGc3hpHWYnlP+KOUxZsazh43WXNHgXpbqw==", + "requires": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1", + "queue-microtask": "^1.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -3343,6 +3781,11 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -3528,6 +3971,32 @@ } } }, + "fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-json-stringify": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.11.tgz", + "integrity": "sha512-J6rw31EvrT/PTZ4xi5Sf/NjYt5jF8tAPVzIi82qmfD4niAwBbHvUB99H6ipHWEaNQKXXpoyG7THBVsbVPo9prw==", + "requires": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + } + }, "fast-redact": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.2.tgz", @@ -3546,11 +4015,70 @@ "punycode": "^1.3.2" } }, + "fastify": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.22.1.tgz", + "integrity": "sha512-TeA4+TzI7VuJrjTNqoxtSXwPEYfCwpT8j9Z3j9WrL8nrt+1bE9G0rP9hLJyvbg4it56p68YsHVhKOee69xyfmA==", + "requires": { + "@fastify/ajv-compiler": "^1.0.0", + "abstract-logging": "^2.0.0", + "avvio": "^7.1.2", + "fast-json-stringify": "^2.5.2", + "fastify-error": "^0.3.0", + "fastify-warning": "^0.2.0", + "find-my-way": "^4.1.0", + "flatstr": "^1.0.12", + "light-my-request": "^4.2.0", + "pino": "^6.13.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.1.4", + "secure-json-parse": "^2.0.0", + "semver": "^7.3.2", + "tiny-lru": "^7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "fastify-cors": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-6.0.2.tgz", + "integrity": "sha512-sE0AOyzmj5hLLRRVgenjA6G2iOGX35/1S3QGYB9rr9TXelMZB3lFrXy4CzwYVOMiujJeMiLgO4J7eRm8sQSv8Q==", + "requires": { + "fastify-plugin": "^3.0.0", + "vary": "^1.1.2" + } + }, + "fastify-error": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz", + "integrity": "sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ==" + }, + "fastify-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", + "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" + }, "fastify-warning": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3574,6 +4102,17 @@ "unpipe": "~1.0.0" } }, + "find-my-way": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.3.3.tgz", + "integrity": "sha512-5E4bRdaATB1MewjOCBjx4xvD205a4t2ripCnXB+YFhYEJ0NABtrcC7XLXLq0TPoFe/WYGUFqys3Qk3HCOGeNcw==", + "requires": { + "fast-decode-uri-component": "^1.0.1", + "fast-deep-equal": "^3.1.3", + "safe-regex2": "^2.0.0", + "semver-store": "^0.3.0" + } + }, "flatstr": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", @@ -3888,6 +4427,11 @@ "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "jszip": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", @@ -3975,6 +4519,35 @@ "immediate": "~3.0.5" } }, + "light-my-request": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.6.0.tgz", + "integrity": "sha512-wQWGwMr7l7fzYPzzzutRoEI1vuREpIpJpTi3t8cHlGdsnBrOF5iR559Bkh+nkDGgnUJtNuuutjnqbxP7zPWKkA==", + "requires": { + "ajv": "^8.1.0", + "cookie": "^0.4.0", + "fastify-warning": "^0.2.0", + "set-cookie-parser": "^2.4.1" + }, + "dependencies": { + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", @@ -4056,6 +4629,14 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "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==", + "requires": { + "yallist": "^4.0.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4413,6 +4994,11 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -4483,6 +5069,11 @@ "rc": "^1.2.8" } }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "responselike": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", @@ -4492,6 +5083,16 @@ "lowercase-keys": "^1.0.0" } }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rfdc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", @@ -4510,6 +5111,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "requires": { + "ret": "~0.2.0" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4551,6 +5160,11 @@ } } }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -4589,6 +5203,11 @@ "send": "0.17.1" } }, + "set-cookie-parser": { + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz", + "integrity": "sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg==" + }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -4640,6 +5259,11 @@ "safe-buffer": "~5.1.0" } }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -4706,6 +5330,11 @@ "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", "dev": true }, + "tiny-lru": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", + "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -4749,13 +5378,26 @@ "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, "trubudget-logging-service": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.2.tgz", - "integrity": "sha512-VAgKZXOEoc3WdB9SFBYlb1xlU7fNFrTNHRu6bm7vfxKtkQHgItBv2b9eQ4nm953V9KFUriSDCJgEunecyBcyPg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trubudget-logging-service/-/trubudget-logging-service-1.1.3.tgz", + "integrity": "sha512-mxX59OXPCkH4Noq+38qh/3bZCxHwwlpwMzJfUtOkFSbeHgXPiGuL8T73O/nB4cfwtnfTrHfN/Hpqx2HCSfhJvg==", "requires": { + "axios": "^0.23.0", + "fastify": "^3.22.1", + "fastify-cors": "^6.0.2", "pino": "^6.13.2", "pino-http": "^5.8.0", "pino-pretty": "^7.0.1" + }, + "dependencies": { + "axios": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "requires": { + "follow-redirects": "^1.14.4" + } + } } }, "type-fest": { @@ -4865,6 +5507,21 @@ "xdg-basedir": "^4.0.0" } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -4940,6 +5597,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "zip-stream": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-3.0.1.tgz", diff --git a/excel-export-service/package.json b/excel-export-service/package.json index 48b2afb8e..b710fbeee 100644 --- a/excel-export-service/package.json +++ b/excel-export-service/package.json @@ -28,7 +28,7 @@ "express": "^4.17.1", "jwt-decode": "^2.2.0", "localized-strings": "^0.2.4", - "trubudget-logging-service": "^1.1.2", + "trubudget-logging-service": "^1.1.3", "url": "^0.11.0" }, "devDependencies": { diff --git a/excel-export-service/src/api.ts b/excel-export-service/src/api.ts index 368094943..8cb57d7d1 100644 --- a/excel-export-service/src/api.ts +++ b/excel-export-service/src/api.ts @@ -1,4 +1,5 @@ import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; +import logger from "./logger"; interface ProjectResponse { apiVersion: string; @@ -79,6 +80,7 @@ export interface Workflowitem { } function getAuthHeader(token: string): AxiosRequestConfig { + logger.trace("Fetching Auth Header ..."); return { headers: { Authorization: token, @@ -91,9 +93,11 @@ export async function getProjects( token: string, base: string, ): Promise { + logger.trace("Fetching projects ..."); const response: AxiosResponse = await axios .get(`${base}/project.list`, getAuthHeader(token)) .catch(function (error) { + logger.error(error); throw error; }); const projectList: Project[] = response.data.data.items.map((i) => i.data); @@ -106,9 +110,11 @@ export async function getSubprojects( token: string, base: string, ): Promise { + logger.trace("Fetching subprojects ..."); const response: AxiosResponse = await axios .get(`${base}/subproject.list?projectId=${projectId}`, getAuthHeader(token)) .catch(function (error) { + logger.error({ err: error }, "Error while getting subprojects"); throw error; }); const subprojectList: Subproject[] = response.data.data.items.map((i) => i.data); @@ -122,12 +128,14 @@ export async function getWorkflowitems( token: string, base: string, ): Promise { + logger.trace("Fetching workflowitems ..."); const response: AxiosResponse = await axios .get( `${base}/workflowitem.list?projectId=${projectId}&subprojectId=${subprojectId}`, getAuthHeader(token), ) .catch(function (error) { + logger.error({ err: error }, "Error while getting workflowitems"); throw error; }); const workflowitemList: Workflowitem[] = response.data.data.workflowitems.map((i) => i.data); @@ -135,6 +143,7 @@ export async function getWorkflowitems( } export async function getApiReadiness(axios: AxiosInstance, base: string): Promise { + logger.trace("Fetching readiness ..."); const response: AxiosResponse = await axios.get(`${base}/readiness`); return response.data; } @@ -144,6 +153,7 @@ export async function getApiVersion( token: string, base: string, ): Promise { + logger.trace("Fetching api version ..."); const response: AxiosResponse = await axios.get(`${base}/version`, getAuthHeader(token)); return response.data; } diff --git a/excel-export-service/src/excel.ts b/excel-export-service/src/excel.ts index 87973b85b..f1a154348 100644 --- a/excel-export-service/src/excel.ts +++ b/excel-export-service/src/excel.ts @@ -1,7 +1,6 @@ import { AxiosInstance } from "axios"; import { Response } from "express"; import * as jwtDecode from "jwt-decode"; -import { Logger } from "pino"; import { getProjects, getSubprojects, @@ -12,6 +11,7 @@ import { } from "./api"; import { amountTypesMapping, statusMapping, workflowItemTypeMapping } from "./helper"; import strings from "./localizeStrings"; +import logger from "./logger"; var Excel = require("exceljs"); @@ -19,12 +19,8 @@ const smallWidth = 20; const mediumWidth = 40; const largeWidth = 60; -export async function writeXLSX( - axios: AxiosInstance, - token: string, - res: Response, - log: Logger, -): Promise { +export async function writeXLSX(axios: AxiosInstance, token: string, res: Response): Promise { + logger.debug("Creating excel sheet"); try { const options = { stream: res, @@ -36,7 +32,7 @@ export async function writeXLSX( workbook.creator = userId ? userId : "Unknown TruBudget User"; workbook.created = new Date(); - // Prepare sheets + logger.trace("Preparing excel sheets"); const projectSheet = workbook.addWorksheet(strings.project.title); const subprojectSheet = workbook.addWorksheet(strings.subproject.title); const workflowitemSheet = workbook.addWorksheet(strings.workflowitem.title); @@ -129,8 +125,10 @@ export async function writeXLSX( ]; const base = res.apiBase; const projects: Project[] = await getProjects(axios, token, base); + logger.trace("Adding projects to sheet... "); for (const project of projects) { + logger.trace({ project }, "Adding row for project"); projectSheet .addRow({ ...project, @@ -149,9 +147,12 @@ export async function writeXLSX( }) .commit(); }); + logger.trace("Adding subprojects to sheet... "); const subprojects: Subproject[] = await getSubprojects(axios, project.id, token, base); for (const subproject of subprojects) { + logger.trace({ subproject }, "adding row for subproject"); + subprojectSheet .addRow({ ...subproject, @@ -175,6 +176,7 @@ export async function writeXLSX( }) .commit(); }); + logger.trace("Adding workflowitems to sheet... "); const workflowitems: Workflowitem[] = await getWorkflowitems( axios, @@ -184,6 +186,7 @@ export async function writeXLSX( base, ); for (const workflowitem of workflowitems) { + logger.trace({ workflowitem }, "Creating row for workflowitem"); workflowitemSheet .addRow({ ...workflowitem, @@ -220,9 +223,10 @@ export async function writeXLSX( } } } + logger.trace("Saving sheet... "); await workbook.commit(); } catch (err) { - log.error({ err }, "Error making request to TruBudget:"); + logger.error({ err }, "Error making request to TruBudget:"); throw new Error(`Error making request to TruBudget: ${err.message}`); } } diff --git a/excel-export-service/src/helper.ts b/excel-export-service/src/helper.ts index c3c82f26f..b6b03c7b9 100644 --- a/excel-export-service/src/helper.ts +++ b/excel-export-service/src/helper.ts @@ -1,6 +1,9 @@ import strings from "./localizeStrings"; +import log from "./logger"; export const statusMapping = (status) => { + log.debug({ status }, "Mapping status"); + switch (status) { case "closed": return strings.common.closed; @@ -12,6 +15,8 @@ export const statusMapping = (status) => { }; export const workflowItemTypeMapping = (type) => { + log.debug({ type }, "Mapping workflowitem type"); + switch (type) { case "general": return strings.common.general; @@ -23,6 +28,8 @@ export const workflowItemTypeMapping = (type) => { }; export const amountTypesMapping = (amountType) => { + log.debug({ amountType }, "Mapping ammount type"); + switch (amountType) { case "N/A": return strings.workflowitem.na; diff --git a/excel-export-service/src/index.ts b/excel-export-service/src/index.ts index ccecca225..5f698da6b 100644 --- a/excel-export-service/src/index.ts +++ b/excel-export-service/src/index.ts @@ -1,20 +1,18 @@ -import * as express from "express"; -import * as cors from "cors"; import axios, { AxiosTransformer } from "axios"; +import * as cors from "cors"; +import * as express from "express"; +import { createPinoExpressLogger } from "trubudget-logging-service"; import * as URL from "url"; - +import { getApiReadiness, getApiVersion } from "./api"; +import { config } from "./config"; import { writeXLSX } from "./excel"; import strings, { languages } from "./localizeStrings"; -import { config } from "./config"; -import { getApiReadiness, getApiVersion } from "./api"; -import { createPinoExpressLogger, createPinoLogger } from "trubudget-logging-service"; +import logger from "./logger"; const DEFAULT_API_VERSION = "1.0"; const API_BASE_PROD = `http://${config.apiHost}:${config.apiPort}/api`; const API_BASE_TEST = `http://${config.testApiHost}:${config.testApiPort}/api`; -const log = createPinoLogger("Excel-Export-Service"); - const transformRequest: AxiosTransformer = (data) => { if (typeof data === "object") { return { @@ -28,7 +26,7 @@ const transformRequest: AxiosTransformer = (data) => { axios.defaults.transformRequest = [transformRequest]; const excelService = express(); -excelService.use(createPinoExpressLogger(log)); +excelService.use(createPinoExpressLogger(logger)); excelService.use(express.json()); excelService.use(cors({ origin: config.accessControlAllowOrigin })); excelService.use((req: express.Request, res: express.Response, next) => { @@ -48,7 +46,7 @@ excelService.get("/readiness", async (req: express.Request, res: express.Respons const ready = await getApiReadiness(axios, res.apiBase); res.status(200).send(ready); } catch (err) { - req.log.error({ err }, "API readiness call failed"); + logger.error({ err }, "API readiness call failed"); res.end(); } }); @@ -70,6 +68,7 @@ excelService.get("/version", (req: express.Request, res: express.Response) => { excelService.get("/download", async (req: express.Request, res: express.Response) => { const token = req.headers.authorization; if (!token) { + req.log.error("No authorization token was provided"); res.status(401).send("Please provide authorization token"); } @@ -77,10 +76,10 @@ excelService.get("/download", async (req: express.Request, res: express.Response await getApiVersion(axios, token, res.apiBase); } catch (err) { if (err.response?.status == 401) { - req.log.error({ err }, "Invalid Token:"); + logger.error({ err }, "Invalid Token:"); res.status(err.response.status).send({ message: err.response.data }); } else { - req.log.error({ err }, "Error validating token"); + logger.error({ err }, "Error validating token"); res.status(err.response.status).send({ message: `Error validating token: ${err.response} ` }); } } @@ -96,23 +95,25 @@ excelService.get("/download", async (req: express.Request, res: express.Response res.setHeader("Content-Disposition", "attachment; filename=TruBudget_Export.xlsx"); res.setHeader("Transfer-Encoding", "chunked"); - await writeXLSX(axios, req.headers.authorization, res, req.log); + await writeXLSX(axios, req.headers.authorization, res); } catch (error) { + req.log.error({ err: error }, "Error while creating excel export"); if (error.response) { res.status(error.response.status).send({ message: error.response.data }); } } }); -excelService.listen(config.serverPort, () => log.info(`App listening on ${config.serverPort}`)); +excelService.listen(config.serverPort, () => logger.info(`App listening on ${config.serverPort}`)); // Enable useful traces of unhandled-promise warnings: process.on("unhandledRejection", (err) => { - log.error({ err }, "UNHANDLED PROMISE REJECTION"); + logger.fatal({ err }, "UNHANDLED PROMISE REJECTION"); process.exit(1); }); function setExcelLanguage(url: string): void { + logger.debug({ url }, "Set excel language based on url"); const queryData = URL.parse(url, true).query; if (queryData.lang) { diff --git a/excel-export-service/src/logger.ts b/excel-export-service/src/logger.ts new file mode 100644 index 000000000..7286a623d --- /dev/null +++ b/excel-export-service/src/logger.ts @@ -0,0 +1,5 @@ +import { createPinoLogger } from "trubudget-logging-service"; + +const logger = createPinoLogger("Excel-Export-Service"); + +export default logger; From 60eaeeb239b94292029d9314a780b338bafa687d Mon Sep 17 00:00:00 2001 From: Mayr Martin <22145802+mayrmartin@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:20:36 +0100 Subject: [PATCH 5/8] logging-service: add enpoint for frontend-logging fix docker --- docs/developer/logging.mdx | 6 + docs/environment-variables.md | 127 +- docs/operation-administration/logging.md | 2 +- frontend/configureServer.sh | 7 + frontend/package-lock.json | 1643 +++++++++++- frontend/package.json | 3 +- frontend/public/env.js | 9 +- frontend/src/index.js | 28 +- frontend/src/logging/console.js | 35 + frontend/src/logging/logger.js | 99 + frontend/src/store.js | 12 +- logging-service/.env.example | 6 + logging-service/Dockerfile | 11 + logging-service/docker-compose.yml | 14 + logging-service/index.js | 20 +- logging-service/package-lock.json | 3086 +++++++++++++++++++++- logging-service/package.json | 12 +- logging-service/serve.js | 106 + scripts/development/.env_example | 16 +- scripts/development/docker-compose.yml | 30 + scripts/development/start-dev.sh | 12 +- scripts/operation/.env_example | 15 + scripts/operation/docker-compose.yml | 23 + scripts/operation/start-trubudget.sh | 11 + 24 files changed, 5123 insertions(+), 210 deletions(-) create mode 100644 frontend/src/logging/console.js create mode 100644 frontend/src/logging/logger.js create mode 100644 logging-service/.env.example create mode 100644 logging-service/Dockerfile create mode 100644 logging-service/docker-compose.yml create mode 100644 logging-service/serve.js mode change 100644 => 100755 scripts/development/start-dev.sh diff --git a/docs/developer/logging.mdx b/docs/developer/logging.mdx index 0639ca5da..5fec5c6eb 100644 --- a/docs/developer/logging.mdx +++ b/docs/developer/logging.mdx @@ -6,3 +6,9 @@ title: Logging import Logging from "./../operation-administration/logging.md"; + +## Frontend Logging + +TruBudget offers the possibility to log frontend events such as errors or crashes. To collect and store this data, the TruBudget logging-service must be enabled. +To enable the TruBudget logging-service, the parameter `--with-frotnend-logging` must be passed when deploying or when starting TruBudget in development mode with the provided start script. +Make sure all env variables are set correctly as defined [in the environment description](./../enviroment-variables.md). diff --git a/docs/environment-variables.md b/docs/environment-variables.md index e65c76bcd..94d5c0508 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -15,64 +15,75 @@ In the following you can find all the environment variables used in the TruBudge If you need a `.env_example` file as a template, use the `.env_example` file in `/scripts/operation`. This file has all values pre-filled. -| Env Variable | Required | Default Value | Used by | Description | -| -------------------------------- | -------- | --------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| ORGANIZATION | yes | - | bc/api | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | -| ORGANIZATION_VAULT_SECRET | yes | - | api | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. Make sure that the api connected to the new node has the same organization vault secret.
**Caution:** If you want to run TruBudget in production,make sure NOT to use the default value from the `.env_example` file! | -| API_HOST | no | | bc/prov | The IP address of one api which is connected to the node you want to connect to (The IP addresses are usually the same as for the P2P host address). | -| API_PORT | no | 8080 | bc/prov | The port used to connect to the api. | -| JWT_SECRET | no | [random] | api/bc | A string that is used to sign JWT which are created by the authenticate endpoint of the api | -| P2P_HOST | no | | bc | The IP address of the blockchain node you want to connect to. | -| P2P_PORT | no | 7447 | bc | The port on which the node you want to connect to has exposed the blockchain. | -| PORT | no | 8080 for api, 8888 for excel | api/export | The port used to expose the API and excel-export for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | -| ROOT_SECRET | no | [random] | api | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | -| RPC_PASSWORD | no | [hardcoded] | api/bc | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every slave node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the RPC_PASSWORD is not required it is highly recommended to set an own secure one | -| RPC_PORT | no | 8000 | api/bc | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | -| RPC_USER | no | multichainrpc | api/bc | The user used to connect to the multichain daemon. | -| EXTERNAL_IP | no | | bc | The IP address with which the current node can be reached. Example: If you have a VM running on 52.52.52.52 and you want to start a slave node from this VM to connect to a master running on 53.53.53.53, you set the `EXTERNAL_IP` to 52.52.52.52 on this node. | -| ACCESS_CONTROL_ALLOW_ORIGIN | no | "\*" | bc/api/excel/storage | Since the services use CORS, the domain by which it can be called needs to be set. Setting this value to `"*"` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). | -| MULTICHAIN_DIR | no | /root | bc | The path to the multichain folder where the blockchain data is persisted. For installations via `docker-compose`, this refers to the path within the docker container of the blockchain. For bare metal installations, this refers to the path on the machine the blockchain is running on. | -| PRETTY_PRINT | no | true | api/bc | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | -| SWAGGER_BASEPATH `deprecated` | no | / | api | This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. | -| TAG | no | master | scripts | The tag defines the version of the image that is pulled from the docker hub. | -| NODE_ENV | no | | ui/api | If set to `development` search Trubudget's external services on localhost, api will allow any string as password. If set to `production` disable Redux devtools extension. | -| REACT_APP_VERSION | no | | ui | Injected version via `$npm_package_version` in`.env` file to ensure the version is shown in the frontend | -| INLINE_RUNTIME_CHUNK | no | false | frontend | Scripts that are injected by React will not be injected inline but as script with src="...". Important for being able to enforce a stricter [Content Security Polify](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) | -| REACT_APP_EMAIL_SERVICE_ENABLED | no | false | ui | When enabled, the frontend requests a email-service readiness call when entering the login screen.
If true the email section in the user-profile is enabled | -| EMAIL_HOST | no | - | ui | IP address of the email notification service | -| EMAIL_PORT | no | 8890 | ui | Port of the email notification service | -| REACT_APP_EXPORT_SERVICE_ENABLED | no | false | ui | If true the frontend requests a export-service readiness call when entering the login screen and
the export button is shown at the side navbar | -| EXPORT_HOST | no | - | ui | IP address of the excel export service | -| EXPORT_PORT | no | 8888 | ui | Port of the excel export service | -| DB_TYPE | no | pg | email | Type of database. A supported list can be found in the [Database Configuration section](#database-configuration) | -| DB_NAME | no | trubudget_email_service | email | Name of the database | -| DB_USER | no | postgres | email | User name for connected database | -| DB_PASSWORD | no | test | email | Password for connected database | -| DB_HOST | no | localhost | email | IP of connected database | -| DB_PORT | no | 5432 | email | Port of connected database | -| DB_SCHEMA | no | public | email | Schema of connected database | -| DB_SSL | no | false | email | If true the DB connection is using the SSL protocol | -| USER_TABLE | no | users | email | Name of the table which is created if the first email address is inserted | -| SMTP_USER | no | testuser | email | This is forwarded to the auth prop of the nodemailer's transport-options, to authenticate with the credentials of the configured SMTP server | -| SMTP_PASSWORD | no | test | email | IP of external SMTP-Server used to actually send notification emails | -| SMTP_HOST | no | localhost | email | IP of external SMTP-Server used to actually send notification emails | -| SMTP_PORT | no | 2500 | email | Port of external SMTP-Server | -| SMTP_SSL | no | false | email | If true the external SMTP-Server connection is using the SSL protocol | -| SQL_DEBUG | no | false | email | The SQL Debug option is forwarded to the knex configuration | -| EMAIL_FROM | no | Trubudget Notification Service | email | This is injected into the `from` field of the email notification | -| EMAIL_SUBJECT | no | Trubudget Notificaiton | email | This is injected into the `subject` field of the email notification | -| EMAIL_TEXT | no | You have received a notification. | email | This is injected into the `body` of the email notification | -| LOG_LEVEL | no | info | all | Defines the log output. Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal` | -| PRETTY_PRINT | no | false | email | Decides whether the logs printed by the email service are pretty printed or not. | -| JWT_SECRET | no | - (required) | email | A secret of min length of 32 - It is used to verify the JWT_TOKEN sent by users of the email-service endpoints | -| AUTHENTICATION | no | JWT | email | If set to none, no JWT-Token is required for all endpoints. If set JWT, a JWT token is necessary | -| STORAGE_SERVICE_HOST | no | localhost | storage | IP address of storage service | -| STORAGE_SERVICE_PORT | no | 8090 | storage | Port of storage service | -| MINIO_ACCESS_KEY | no | minio | storage | Access key for Minio server | -| MINIO_SECRET_KEY | no | minio123 | storage | Secret (Password) for Minio server | -| MINIO_PORT | no | 9000 | storage | Port of connected Minio | -| MINIO_HOST | no | localhost | storage | IP address of connected Minio server | -| MINIO_BUCKET_NAME | no | trubudget | storage | Bucket name of the connected Minio server | +| Env Variable | Required | Default Value | Used by | Description | +| ---------------------------------- | -------- | --------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------- | +| ORGANIZATION | yes | - | bc/api | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | +| ORGANIZATION_VAULT_SECRET | yes | - | api | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. Make sure that the api connected to the new node has the same organization vault secret.
**Caution:** If you want to run TruBudget in production,make sure NOT to use the default value from the `.env_example` file! | +| API_HOST | no | | bc/prov | The IP address of one api which is connected to the node you want to connect to (The IP addresses are usually the same as for the P2P host address). | +| API_PORT | no | 8080 | bc/prov | The port used to connect to the api. | +| JWT_SECRET | no | [random] | api/bc | A string that is used to sign JWT which are created by the authenticate endpoint of the api | +| P2P_HOST | no | | bc | The IP address of the blockchain node you want to connect to. | +| P2P_PORT | no | 7447 | bc | The port on which the node you want to connect to has exposed the blockchain. | +| PORT | no | 8080 for api, 8888 for excel | api/export | The port used to expose the API and excel-export for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | +| ROOT_SECRET | no | [random] | api | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | +| RPC_PASSWORD | no | [hardcoded] | api/bc | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every slave node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the RPC_PASSWORD is not required it is highly recommended to set an own secure one | +| RPC_PORT | no | 8000 | api/bc | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | +| RPC_USER | no | multichainrpc | api/bc | The user used to connect to the multichain daemon. | +| EXTERNAL_IP | no | | bc | The IP address with which the current node can be reached. Example: If you have a VM running on 52.52.52.52 and you want to start a slave node from this VM to connect to a master running on 53.53.53.53, you set the `EXTERNAL_IP` to 52.52.52.52 on this node. | +| ACCESS_CONTROL_ALLOW_ORIGIN | no | "\*" | bc/api/excel/storage | Since the services use CORS, the domain by which it can be called needs to be set. Setting this value to `"*"` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). | +| MULTICHAIN_DIR | no | /root | bc | The path to the multichain folder where the blockchain data is persisted. For installations via `docker-compose`, this refers to the path within the docker container of the blockchain. For bare metal installations, this refers to the path on the machine the blockchain is running on. | +| PRETTY_PRINT | no | true | \* | Decides whether the logs printed by services are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | +| SWAGGER_BASEPATH `deprecated` | no | / | api | This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. | +| TAG | no | master | scripts | The tag defines the version of the image that is pulled from the docker hub. | +| NODE_ENV | no | | ui/api | If set to `development` search Trubudget's external services on localhost, api will allow any string as password. If set to `production` disable Redux devtools extension. | +| REACT_APP_VERSION | no | | ui | Injected version via `$npm_package_version` in`.env` file to ensure the version is shown in the frontend | +| INLINE_RUNTIME_CHUNK | no | false | frontend | Scripts that are injected by React will not be injected inline but as script with src="...". Important for being able to enforce a stricter [Content Security Polify](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) | +| REACT_APP_EMAIL_SERVICE_ENABLED | no | false | ui | When enabled, the frontend requests a email-service readiness call when entering the login screen.
If true the email section in the user-profile is enabled | +| EMAIL_HOST | no | - | ui | IP address of the email notification service | +| EMAIL_PORT | no | 8890 | ui | Port of the email notification service | +| REACT_APP_EXPORT_SERVICE_ENABLED | no | false | ui | If true the frontend requests a export-service readiness call when entering the login screen and
the export button is shown at the side navbar | +| EXPORT_HOST | no | - | ui | IP address of the excel export service | +| EXPORT_PORT | no | 8888 | ui | Port of the excel export service | +| REACT_APP_LOGGING | no | false | ui | When enabled, the log/erro messages on the frontend are send back to the logging-serverice | +| REACT_APP_LOG_LEVEL | no | trace | ui | Values are: info | error | trace. When set to trace all state transitions of the ui are loged to console & sent back to the logging-service | +| REACT_APP_LOGGING_SERVICE_HOST | no | localhost | ui | Host of the logging-service | +| REACT_APP_LOGGING_SERVICE_PORT | no | 3000 | ui | Port on wich the logging-service is listening | +| REACT_APP_LOGGING_SERVICE_HOST_SSL | no | false | ui | Defines whether the frontend logger uses SSL to comunicate with the logging-service. When used in production, SSL _must_ be enabled! | +| REACT_APP_LOGGING_PUSH_INTERVAL | no | 20 | ui | Defines in which intervals the collected log-messages should be pusht to the logging-service (in seconds). | +| DB_TYPE | no | pg | email | Type of database. A supported list can be found in the [Database Configuration section](#database-configuration) | +| DB_NAME | no | trubudget_email_service | email | Name of the database | +| DB_USER | no | postgres | email | User name for connected database | +| DB_PASSWORD | no | test | email | Password for connected database | +| DB_HOST | no | localhost | email | IP of connected database | +| DB_PORT | no | 5432 | email | Port of connected database | +| DB_SCHEMA | no | public | email | Schema of connected database | +| DB_SSL | no | false | email | If true the DB connection is using the SSL protocol | +| USER_TABLE | no | users | email | Name of the table which is created if the first email address is inserted | +| SMTP_USER | no | testuser | email | This is forwarded to the auth prop of the nodemailer's transport-options, to authenticate with the credentials of the configured SMTP server | +| SMTP_PASSWORD | no | test | email | IP of external SMTP-Server used to actually send notification emails | +| SMTP_HOST | no | localhost | email | IP of external SMTP-Server used to actually send notification emails | +| SMTP_PORT | no | 2500 | email | Port of external SMTP-Server | +| SMTP_SSL | no | false | email | If true the external SMTP-Server connection is using the SSL protocol | +| SQL_DEBUG | no | false | email | The SQL Debug option is forwarded to the knex configuration | +| EMAIL_FROM | no | Trubudget Notification Service👻 | email | This is injected into the `from` field of the email notification | +| EMAIL_SUBJECT | no | Trubudget Notificaiton | email | This is injected into the `subject` field of the email notification | +| EMAIL_TEXT | no | You have received a notification. | email | This is injected into the `body` of the email notification | +| LOG_LEVEL | no | info | all | Defines the log output. Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal` | +| JWT_SECRET | no | - (required) | email | A secret of min length of 32 - It is used to verify the JWT_TOKEN sent by users of the email-service endpoints | +| AUTHENTICATION | no | JWT | email | If set to none, no JWT-Token is required for all endpoints. If set JWT, a JWT token is necessary | +| STORAGE_SERVICE_HOST | no | localhost | storage | IP address of storage service | +| STORAGE_SERVICE_PORT | no | 8090 | storage | Port of storage service | +| MINIO_ACCESS_KEY | no | minio | storage | Access key for Minio server | +| MINIO_SECRET_KEY | no | minio123 | storage | Secret (Password) for Minio server | +| MINIO_PORT | no | 9000 | storage | Port of connected Minio | +| MINIO_HOST | no | localhost | storage | IP address of connected Minio server | +| MINIO_BUCKET_NAME | no | trubudget | storage | Bucket name of the connected Minio server | +| LOGGER_PORT | yes | 3000 | logging | Port where the logging-service is exposed | +| API_HOST | yes | localhost | logging | API host | +| API_PORT | yes | 8080 | logging | API port | +| LOG_LEVEL | yes | trace | logging | Log-level | +| LOGGING_SERVICE_CACHE_DURATION | yes | 20 | logging | Defines how long valid JWT should be cached (in minutes). | +| LOGGING_SERVICE_NODE_ENV | yes | production | logging | Environment |