From d25d91dd172a952fe8d8de97e3739ba9432ace2b Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 17 Nov 2025 15:13:01 +0600 Subject: [PATCH 1/2] chore: prepare PR snapshot of devdgna/pipeline-implementation relative to credebl/platform@main\n\n- Base: upstream/main\n- Snapshot: contents from fork branch pipeline-implementation\n- Purpose: enable opening a PR from fork to upstream despite unrelated histories --- .dockerignore | 1 - .env.demo | 222 - .env.sample | 90 +- .eslintrc.js | 2 +- .github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml | 73 - .github/ISSUE_TEMPLATE/bug_report.md | 31 + .github/ISSUE_TEMPLATE/bug_report.yml | 90 - .github/dco.yml | 3 - .github/workflows/continuous-delivery.yml | 69 - .github/workflows/dev-agent-provisioning.yml | 142 + .github/workflows/dev-agent.yml | 132 + .github/workflows/dev-api-gateway.yml | 132 + .github/workflows/dev-connection.yml | 128 + .github/workflows/dev-ecosystem.yml | 128 + .github/workflows/dev-issuance.yml | 128 + .github/workflows/dev-ledger.yml | 128 + .../workflows/dev-notification-webhook.yml | 128 + .github/workflows/dev-notification.yml | 128 + .github/workflows/dev-organization.yml | 128 + .github/workflows/dev-user.yml | 128 + .github/workflows/dev-utility.yml | 128 + .github/workflows/dev-verification.yml | 128 + .github/workflows/dev-webhook.yml | 128 + .github/workflows/prod-agent-provisioning.yml | 141 + .github/workflows/prod-agent.yml | 129 + .github/workflows/prod-api-gateway.yml | 128 + .github/workflows/prod-cloud-wallet.yml | 137 + .github/workflows/prod-connection.yml | 125 + .github/workflows/prod-ecosystem.yml | 125 + .github/workflows/prod-issuance.yml | 125 + .github/workflows/prod-ledger.yml | 125 + .github/workflows/prod-notification.yml | 125 + .github/workflows/prod-organization.yml | 125 + .github/workflows/prod-seed.yml | 140 + .github/workflows/prod-user.yml | 125 + .github/workflows/prod-utility.yml | 127 + .github/workflows/prod-verification.yml | 125 + .github/workflows/prod-webhook.yml | 125 + .github/workflows/qa-agent-provisioning.yml | 141 + .github/workflows/qa-agent.yml | 129 + .github/workflows/qa-api-gateway.yml | 129 + .github/workflows/qa-cloud-wallet.yml | 124 + .github/workflows/qa-connection.yml | 125 + .github/workflows/qa-ecosystem.yml | 125 + .github/workflows/qa-issuance.yml | 126 + .github/workflows/qa-ledger.yml | 125 + .github/workflows/qa-notification.yml | 125 + .github/workflows/qa-organization.yml | 125 + .github/workflows/qa-seed.yml | 125 + .github/workflows/qa-user.yml | 125 + .github/workflows/qa-utility.yml | 125 + .github/workflows/qa-verification.yml | 125 + .github/workflows/qa-webhook.yml | 125 + .github/workflows/scorecard.yml | 73 - .../workflows/stage-agent-provisioning.yml | 139 + .github/workflows/stage-agent.yml | 128 + .github/workflows/stage-api-gateway.yml | 129 + .github/workflows/stage-cloud-wallet.yml | 137 + .github/workflows/stage-connection.yml | 128 + .github/workflows/stage-ecosystem.yml | 125 + .github/workflows/stage-issuance.yml | 127 + .github/workflows/stage-ledger.yml | 125 + .github/workflows/stage-notification.yml | 125 + .github/workflows/stage-organization.yml | 125 + .github/workflows/stage-seed.yml | 125 + .github/workflows/stage-user.yml | 125 + .github/workflows/stage-utility.yml | 125 + .github/workflows/stage-verification.yml | 126 + .github/workflows/stage-webhook.yml | 125 + .gitignore | 2 +- .husky/commit-msg | 17 - .husky/post-commit | 42 - .husky/pre-push | 91 - .nvmrc | 1 + Dockerfiles/Dockerfile.agent-provisioning | 25 +- Dockerfiles/Dockerfile.agent-service | 19 +- Dockerfiles/Dockerfile.api-gateway | 19 +- Dockerfiles/Dockerfile.cloud-wallet | 26 +- Dockerfiles/Dockerfile.connection | 23 +- Dockerfiles/Dockerfile.ecosystem | 44 + Dockerfiles/Dockerfile.geolocation | 15 +- Dockerfiles/Dockerfile.issuance | 23 +- Dockerfiles/Dockerfile.ledger | 23 +- Dockerfiles/Dockerfile.notification | 17 +- Dockerfiles/Dockerfile.organization | 24 +- Dockerfiles/Dockerfile.seed | 7 +- Dockerfiles/Dockerfile.user | 43 +- Dockerfiles/Dockerfile.utility | 23 +- Dockerfiles/Dockerfile.verification | 23 +- Dockerfiles/Dockerfile.webhook | 23 +- LICENSE | 2 +- README.md | 75 +- agent.env | 178 - ...160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.yaml | 23 + ...4765-9669-cfb8d852f9ff_Platform-admin.yaml | 23 + .../AFJ/port-file/last-admin-port.txt | 2 +- .../AFJ/port-file/last-inbound-port.txt | 2 +- .../AFJ/scripts/docker_start_agent.sh | 32 +- .../agent-provisioning/AFJ/scripts/fargate.sh | 498 +- .../AFJ/scripts/on_premises_agent.sh | 11 +- .../AFJ/scripts/start_agent.sh | 13 +- .../AFJ/scripts/start_agent_ecs.sh | 188 +- .../src/agent-provisioning.module.ts | 22 +- .../src/agent-provisioning.service.ts | 31 +- apps/agent-provisioning/src/main.ts | 2 - .../src/agent-service.controller.ts | 136 +- .../agent-service/src/agent-service.module.ts | 17 +- .../src/agent-service.service.ts | 741 +- .../src/interface/agent-service.interface.ts | 41 +- apps/agent-service/src/main.ts | 2 - .../repositories/agent-service.repository.ts | 873 +- apps/agent-service/test/app.e2e-spec.ts | 6 +- apps/api-gateway/common/exception-handler.ts | 16 +- apps/api-gateway/common/interface.ts | 22 +- .../agent-service/agent-service.controller.ts | 323 +- .../src/agent-service/agent-service.module.ts | 3 +- .../agent-service/agent-service.service.ts | 187 +- .../agent-service/dto/agent-service.dto.ts | 146 +- .../interface/agent-service.interface.ts | 37 +- .../api-gateway/src/agent/agent.controller.ts | 144 +- apps/api-gateway/src/agent/agent.module.ts | 3 +- apps/api-gateway/src/agent/agent.service.ts | 148 +- apps/api-gateway/src/app.controller.spec.ts | 1 + apps/api-gateway/src/app.module.ts | 29 +- apps/api-gateway/src/app.service.ts | 10 +- .../api-gateway/src/authz/authz.controller.ts | 450 +- apps/api-gateway/src/authz/authz.module.ts | 26 +- apps/api-gateway/src/authz/authz.service.ts | 97 +- .../src/authz/decorators/roles.decorator.ts | 4 + .../src/authz/decorators/user-auth-client.ts | 26 - .../src/authz/dtos/auth-token-res.dto.ts | 1 + .../src/authz/dtos/forgot-password.dto.ts | 52 +- .../src/authz/dtos/reset-password.dto.ts | 2 +- .../src/authz/dtos/user-logout.dto.ts | 15 - .../src/authz/guards/ecosystem-roles.guard.ts | 74 + .../src/authz/guards/session.guard.ts | 19 - .../src/authz/guards/user-role.guard.ts | 5 +- .../src/authz/jwt-payload.interface.ts | 26 +- apps/api-gateway/src/authz/jwt.strategy.ts | 52 +- .../src/authz/mobile-jwt.strategy.ts | 5 +- apps/api-gateway/src/authz/socket.gateway.ts | 4 +- .../cloud-wallet/cloud-wallet.controller.ts | 1343 +- .../src/cloud-wallet/cloud-wallet.module.ts | 3 +- .../src/cloud-wallet/cloud-wallet.service.ts | 269 +- .../accept-proof-request-with-cred.dto.ts | 32 + .../src/cloud-wallet/dtos/cloudWallet.dto.ts | 74 +- .../dtos/configure-base-wallet.dto.ts | 23 +- .../dtos/decline-proof-request.dto.ts | 24 + .../dtos/self-attested-credential.dto.ts | 49 + .../cloud-wallet/enums/connections.enum.ts | 4 +- .../src/connection/connection.controller.ts | 659 +- .../src/connection/connection.module.ts | 3 +- .../src/connection/connection.service.ts | 96 +- .../src/connection/enums/connections.enum.ts | 4 +- .../src/connection/interfaces/index.ts | 4 +- .../credential-definition.controller.spec.ts | 5 +- .../credential-definition.controller.ts | 114 +- .../credential-definition.module.ts | 3 +- .../credential-definition.service.ts | 27 +- .../dto/create-cred-defs.dto.ts | 12 +- .../dto/get-all-platform-cred-defs.dto.ts | 4 +- .../src/dtos/apiResponse.dto copy.ts | 1 + apps/api-gateway/src/dtos/authDto.dto.ts | 2 +- .../src/dtos/connection-out-of-band.dto.ts | 3 +- apps/api-gateway/src/dtos/connection.dto.ts | 1 + .../dtos/create-revocation-registry.dto.ts | 1 + .../api-gateway/src/dtos/create-schema.dto.ts | 492 +- .../src/dtos/created-response-dto.ts | 2 +- .../src/dtos/credential-offer.dto.ts | 4 +- .../src/dtos/credential-send-offer.dto.ts | 4 +- .../src/dtos/email-validator.dto.ts | 2 +- .../src/dtos/forgot-password.dto.ts | 2 +- apps/api-gateway/src/dtos/geo-location-dto.ts | 23 - .../src/dtos/holder-details.dto.ts | 2 +- .../src/dtos/holder-reset-password.ts | 2 +- .../src/dtos/issue-credential-offer.dto .ts | 3 +- .../dtos/issue-credential-out-of-band.dto.ts | 3 +- .../src/dtos/not-found-error.dto.ts | 2 +- .../src/dtos/register-non-admin-user.dto.ts | 2 +- .../src/dtos/register-tenant.dto.ts | 2 +- .../api-gateway/src/dtos/register-user.dto.ts | 2 +- .../src/dtos/reset-password.dto.ts | 2 +- .../src/dtos/revoke-credential.dto.ts | 1 + .../src/dtos/save-roles-permissions.dto.ts | 12 +- apps/api-gateway/src/dtos/schemasearch.dto.ts | 1 + .../dtos/update-revocation-registry.dto.ts | 1 + .../dtos/accept-reject-invitations.dto.ts | 25 + .../ecosystem/dtos/add-organizations.dto.ts | 20 + .../ecosystem/dtos/create-ecosystem-dto.ts | 50 + .../decline-endorsement-transaction.dto.ts | 6 + .../ecosystem/dtos/delete-invitations.dto.ts | 19 + .../src/ecosystem/dtos/edit-ecosystem-dto.ts | 49 + .../dtos/get-all-endorsements.dto.ts | 35 + .../dtos/get-all-received-invitations.dto.ts | 13 + .../src/ecosystem/dtos/get-members.dto.ts | 27 + .../src/ecosystem/dtos/request-schema.dto.ts | 208 + .../src/ecosystem/dtos/send-invitation.dto.ts | 26 + .../src/ecosystem/ecosystem.controller.ts | 706 + .../src/ecosystem/ecosystem.module.ts | 29 + .../src/ecosystem/ecosystem.service.ts | 214 + apps/api-gateway/src/fido/fido.controller.ts | 458 +- apps/api-gateway/src/fido/fido.module.ts | 3 +- apps/api-gateway/src/fido/fido.service.ts | 99 +- .../geo-location/geo-location.controller.ts | 21 +- .../src/geo-location/geo-location.module.ts | 4 +- .../src/geo-location/geo-location.service.ts | 14 +- .../src/helper-files/file-operation.helper.ts | 4 +- .../src/issuance/dtos/issuance.dto.ts | 35 +- .../src/issuance/interfaces/index.ts | 3 +- .../src/issuance/issuance.controller.ts | 779 +- .../src/issuance/issuance.module.ts | 6 +- .../src/issuance/issuance.service.ts | 369 +- apps/api-gateway/src/main.ts | 57 +- .../notification/notification.controller.ts | 127 +- .../src/notification/notification.module.ts | 3 +- .../src/notification/notification.service.ts | 52 +- .../src/organization/dtos/client-token.dto.ts | 24 - .../dtos/create-organization-dto.ts | 29 +- .../organization/dtos/send-invitation.dto.ts | 4 +- .../dtos/update-organization-dto.ts | 81 +- .../organization/organization.controller.ts | 526 +- .../src/organization/organization.module.ts | 8 +- .../src/organization/organization.service.ts | 141 +- .../src/platform/platform.controller.spec.ts | 3 +- .../src/platform/platform.controller.ts | 42 +- .../src/platform/platform.module.ts | 4 +- .../src/platform/platform.service.ts | 59 +- .../src/revocation/revocation.module.ts | 27 +- .../src/revocation/revocation.service.ts | 80 +- .../src/schema/dtos/get-all-schema.dto.ts | 236 +- .../src/schema/dtos/update-schema-dto.ts | 23 - .../src/schema/schema.controller.spec.ts | 7 +- .../src/schema/schema.controller.ts | 156 +- apps/api-gateway/src/schema/schema.module.ts | 3 +- apps/api-gateway/src/schema/schema.service.ts | 47 +- apps/api-gateway/src/tracer.ts | 69 - apps/api-gateway/src/user/dto/add-user.dto.ts | 49 +- .../src/user/dto/create-user.dto.ts | 50 +- .../src/user/dto/login-user.dto.ts | 31 +- .../src/user/dto/share-certificate.dto.ts | 35 + .../user/dto/update-platform-settings.dto.ts | 16 +- apps/api-gateway/src/user/user.controller.ts | 202 +- apps/api-gateway/src/user/user.module.ts | 3 +- apps/api-gateway/src/user/user.service.ts | 62 +- apps/api-gateway/src/user/utils/index.ts | 46 - .../src/utilities/utilities.controller.ts | 63 +- .../src/utilities/utilities.module.ts | 8 +- .../src/utilities/utilities.service.ts | 14 +- .../src/verification/dto/request-proof.dto.ts | 150 +- .../src/verification/dto/webhook-proof.dto.ts | 10 +- .../verification/enum/verification.enum.ts | 23 - .../interfaces/verification.interface.ts | 2 +- .../verification/verification.controller.ts | 714 +- .../src/verification/verification.module.ts | 6 +- .../src/verification/verification.service.ts | 243 +- .../src/webhook/dtos/get-webhoook-dto.ts | 3 +- .../src/webhook/webhook.controller.ts | 123 +- .../api-gateway/src/webhook/webhook.module.ts | 3 +- .../src/webhook/webhook.service.ts | 18 +- .../src/cloud-wallet.controller.ts | 114 +- apps/cloud-wallet/src/cloud-wallet.module.ts | 40 +- .../src/cloud-wallet.repository.ts | 112 +- apps/cloud-wallet/src/cloud-wallet.service.ts | 702 +- apps/cloud-wallet/src/main.ts | 3 +- apps/connection/src/connection.controller.ts | 6 +- apps/connection/src/connection.module.ts | 9 +- apps/connection/src/connection.repository.ts | 48 +- apps/connection/src/connection.service.ts | 346 +- .../src/interfaces/connection.interfaces.ts | 4 +- apps/connection/src/main.ts | 4 +- .../accept-reject-ecosysteminvitation.dto.ts | 11 + apps/ecosystem/dtos/send-invitation.dto.ts | 12 + .../dtos/update-ecosystem-invitation.dto.ts | 8 + .../dtos/update-ecosystemOrgs.dto.ts | 8 + apps/ecosystem/enums/ecosystem.enum.ts | 36 + .../interfaces/ecosystem.interfaces.ts | 487 + .../interfaces/ecosystemMembers.interface.ts | 9 + .../interfaces/endorsements.interface.ts | 36 + .../interfaces/invitations.interface.ts | 9 + .../src/ecosystem.controller.spec.ts | 22 + apps/ecosystem/src/ecosystem.controller.ts | 244 + apps/ecosystem/src/ecosystem.module.ts | 29 + apps/ecosystem/src/ecosystem.repository.ts | 1483 + apps/ecosystem/src/ecosystem.service.ts | 2209 ++ apps/ecosystem/src/main.ts | 23 + .../DeleteEcosystemMemberTemplate.ts | 50 + .../templates/EcosystemInviteTemplate.ts | 83 + apps/ecosystem/test/app.e2e-spec.ts | 22 + apps/ecosystem/test/jest-e2e.json | 9 + apps/ecosystem/tsconfig.app.json | 9 + apps/geo-location/src/geo-location.module.ts | 6 - apps/geo-location/src/main.ts | 2 - apps/issuance/enum/issuance.enum.ts | 33 +- .../interfaces/issuance.interfaces.ts | 145 +- .../libs/helpers/attributes.extractor.ts | 320 - .../libs/helpers/attributes.validator.ts | 16 +- apps/issuance/src/issuance.controller.ts | 109 +- apps/issuance/src/issuance.module.ts | 34 +- apps/issuance/src/issuance.processor.ts | 10 +- apps/issuance/src/issuance.repository.ts | 229 +- apps/issuance/src/issuance.service.ts | 1578 +- apps/issuance/src/main.ts | 3 +- .../out-of-band-issuance.template.ts | 2 +- .../ledger/libs/helpers/w3c.schema.builder.ts | 434 - .../credential-definition.controller.ts | 7 +- .../credential-definition.module.ts | 4 +- .../credential-definition.service.ts | 741 +- .../create-credential-definition.interface.ts | 15 - .../credential-definition.repository.ts | 32 +- apps/ledger/src/ledger.controller.ts | 6 - apps/ledger/src/ledger.module.ts | 6 - apps/ledger/src/ledger.service.ts | 20 - apps/ledger/src/main.ts | 3 - .../src/repositories/ledger.repository.ts | 52 - apps/ledger/src/schema/enum/schema.enum.ts | 2 +- .../interfaces/schema-payload.interface.ts | 34 - .../src/schema/interfaces/schema.interface.ts | 64 +- .../schema/repositories/schema.repository.ts | 162 +- apps/ledger/src/schema/schema.controller.ts | 34 +- apps/ledger/src/schema/schema.interface.ts | 3 - apps/ledger/src/schema/schema.module.ts | 4 +- apps/ledger/src/schema/schema.service.ts | 856 +- apps/notification/src/main.ts | 4 +- apps/notification/src/notification.module.ts | 6 - apps/organization/dtos/client-token.dto.ts | 7 - .../interfaces/organization.interface.ts | 163 +- .../repositories/organization.repository.ts | 379 +- apps/organization/src/main.ts | 2 - .../src/organization.controller.ts | 137 +- apps/organization/src/organization.module.ts | 11 +- apps/organization/src/organization.service.ts | 1026 +- .../organization-invitation.template.ts | 39 +- apps/user/dtos/login-user.dto.ts | 57 +- apps/user/interfaces/user.interface.ts | 174 +- apps/user/repositories/user.repository.ts | 484 +- apps/user/src/fido/fido.module.ts | 4 +- apps/user/src/fido/fido.service.ts | 13 +- apps/user/src/main.ts | 3 +- apps/user/src/user.controller.ts | 218 +- apps/user/src/user.module.ts | 13 +- apps/user/src/user.service.ts | 918 +- apps/user/templates/arbiter-template.ts | 97 + apps/user/templates/degree-template.ts | 248 + apps/user/templates/event-certificates.ts | 82 + apps/user/templates/event-pinnacle.ts | 80 + apps/user/templates/participant-template.ts | 93 + .../user/templates/reset-password-template.ts | 25 +- apps/user/templates/user-email-template.ts | 28 +- apps/user/templates/winner-template.ts | 98 + apps/user/templates/world-record-template.ts | 95 + apps/user/test/app.e2e-spec.ts | 6 +- apps/utility/src/main.ts | 2 - apps/utility/src/utilities.module.ts | 6 - apps/utility/src/utilities.repository.ts | 6 +- .../src/interfaces/verification.interface.ts | 366 +- apps/verification/src/main.ts | 3 +- .../repositories/verification.repository.ts | 211 +- .../src/verification.controller.ts | 85 +- apps/verification/src/verification.module.ts | 20 +- apps/verification/src/verification.service.ts | 751 +- .../out-of-band-verification.template.ts | 14 +- apps/verification/tsconfig.app.json | 1 + apps/webhook/src/main.ts | 3 +- apps/webhook/src/webhook.module.ts | 8 +- apps/webhook/src/webhook.repository.ts | 5 +- compass.yml | 36 - docker-compose-dev.yml | 200 - docker-compose.nats.yml | 12 - docker-compose.redis.yml | 12 - docker-compose.yml | 96 +- libs/aws/package.json | 26 - libs/aws/src/aws.service.ts | 2 +- libs/aws/tsconfig.build.json | 7 - libs/aws/tsconfig.json | 4 - libs/{config => aws}/tsconfig.lib.json | 2 +- .../src/client-registration.service.ts | 729 +- .../src/dtos/accessTokenPayloadDto.ts | 1 + .../client-credential-token-payload.dto.ts | 1 + .../src/dtos/client-token.dto.ts | 6 - .../src/dtos/create-user.dto.ts | 21 + .../src/dtos/userTokenPayloadDto.ts | 1 + libs/common/package.json | 39 - libs/common/src/NATSClient.ts | 67 - libs/common/src/cast.helper.ts | 209 +- libs/common/src/common.constant.ts | 286 +- libs/common/src/common.module.ts | 12 +- libs/common/src/common.service.ts | 553 +- libs/common/src/common.utils.ts | 64 +- libs/common/src/did.validator.ts | 40 + libs/common/src/email.service.ts | 37 - libs/common/src/index.ts | 1 - .../src/interfaces/cloud-wallet.interface.ts | 228 +- .../src/interfaces/connection.interface.ts | 28 + .../src/interfaces/ecosystem.interface.ts | 61 + libs/common/src/interfaces/interface.ts | 53 +- .../src/interfaces/issuance.interface.ts | 4 +- .../src/interfaces/organization.interface.ts | 1 + .../src/interfaces/response.interface.ts | 14 +- .../common/src/interfaces/schema.interface.ts | 204 +- libs/common/src/interfaces/user.interface.ts | 129 +- .../src/interfaces/verification.interface.ts | 99 +- libs/common/src/nats.config.ts | 8 +- libs/common/src/nats.interceptor.ts | 21 - libs/common/src/resend-helper-file.ts | 31 - libs/common/src/response-messages/index.ts | 195 +- libs/common/src/send-grid-helper-file.ts | 16 +- libs/common/src/validator.ts | 70 - libs/common/tsconfig.build.json | 7 - libs/common/tsconfig.json | 6 - libs/{logger => common}/tsconfig.lib.json | 2 +- libs/config/src/config.module.ts | 16 - libs/config/src/config.service.ts | 28 - libs/config/src/global-config.module.ts | 14 - libs/config/src/index.ts | 3 - libs/context/src/contextInterceptorModule.ts | 45 - libs/context/src/contextModule.ts | 29 - .../src/contextStorageService.interface.ts | 8 - libs/context/src/index.ts | 2 - .../src/nestjsClsContextStorageService.ts | 27 - libs/entities/base.number.entity.ts | 19 + libs/entities/base.system.entity.ts | 25 + libs/enum/src/enum.ts | 320 +- .../image-service/src/image-service.module.ts | 8 + .../src/image-service.service.spec.ts | 18 + .../src/image-service.service.ts | 16 + libs/image-service/src/index.ts | 2 + libs/image-service/tsconfig.lib.json | 9 + libs/keycloak-url/src/keycloak-url.service.ts | 106 +- libs/logger/src/index.ts | 2 - libs/logger/src/log.ts | 27 - libs/logger/src/logger.interface.ts | 20 - libs/logger/src/logger.module.ts | 77 - libs/logger/src/logger.service.ts | 128 - libs/logger/src/logging.interceptor.ts | 45 - libs/logger/src/nestjsLoggerServiceAdapter.ts | 41 - .../logger/src/transports/consoleTransport.ts | 79 - libs/logger/src/transports/fileTransport.ts | 14 - libs/logger/src/winstonLogger.ts | 142 - .../credebl-master-table.json | 112 +- .../migration.sql | 50 - .../migration.sql | 8 - .../migration.sql | 2 - .../migration.sql | 12 + .../migration.sql | 2 - .../migration.sql | 8 + .../migration.sql | 2 + .../migration.sql | 2 - .../migration.sql | 4 + .../migration.sql | 11 + .../migration.sql | 10 - .../migration.sql | 2 - .../migration.sql | 56 - .../migration.sql | 2 - .../migration.sql | 14 - .../migration.sql | 2 - .../migration.sql | 2 - .../migration.sql | 51 - .../migration.sql | 24 - .../migration.sql | 2 - .../migration.sql | 2 - .../migration.sql | 2 - libs/prisma-service/prisma/schema.prisma | 180 +- .../scripts/update_client_credential_data.sh | 2 +- libs/prisma-service/prisma/seed.ts | 859 +- .../src/prisma-service.service.ts | 2 + libs/push-notifications/src/index.ts | 2 + .../src/push-notifications.module.ts | 8 + .../src/push-notifications.service.spec.ts | 18 + .../src/push-notifications.service.ts | 45 + libs/push-notifications/tsconfig.lib.json | 9 + libs/response/src/index.ts | 2 + libs/response/src/response.module.ts | 8 + libs/response/src/response.service.spec.ts | 18 + libs/response/src/response.service.ts | 23 + libs/{context => response}/tsconfig.lib.json | 2 +- libs/service/base.service.ts | 30 + libs/supabase/src/supabase.service.ts | 3 +- libs/user-activity/repositories/index.ts | 144 +- .../user-activity/src/user-activity.module.ts | 2 - libs/validations/arrayKeyword.ts | 3 - libs/validations/exclusiveMaximum.ts | 28 - libs/validations/exclusiveMinimum.ts | 28 - libs/validations/keyword.ts | 7 - libs/validations/maxItems.ts | 28 - libs/validations/maxLength.ts | 28 - libs/validations/maximum.ts | 28 - libs/validations/minItems.ts | 28 - libs/validations/minLength.ts | 28 - libs/validations/minimum.ts | 28 - libs/validations/multipleOf.ts | 28 - libs/validations/numberKeyword.ts | 3 - libs/validations/objectKeyword.ts | 3 - libs/validations/pattern.ts | 33 - libs/validations/stringKeyword.ts | 3 - libs/validations/uniqueItems.ts | 28 - nats-server.conf | 6 - nest-cli.json | 74 +- package-lock.json | 25497 ++++++++++++++++ package.json | 93 +- pnpm-lock.yaml | 10876 +++---- pnpm-workspace.yaml | 27 - prod-taskdef/agent-provisioning.json | 80 + prod-taskdef/agent-service.json | 43 + prod-taskdef/api-gateway-service.json | 49 + prod-taskdef/cloud-wallet-service.json | 43 + prod-taskdef/connection-service.json | 43 + prod-taskdef/ecosystem-service.json | 43 + prod-taskdef/issuance-service.json | 43 + prod-taskdef/ledger-service.json | 43 + prod-taskdef/notification-service.json | 43 + .../notification-webhook-service.json | 43 + prod-taskdef/organization-service.json | 43 + prod-taskdef/seed.json | 43 + prod-taskdef/user-service.json | 43 + prod-taskdef/utility-service.json | 43 + prod-taskdef/verification-service.json | 43 + prod-taskdef/webhook-service.json | 43 + qa-taskdef/agent-provisioning.json | 54 + qa-taskdef/agent-service.json | 43 + qa-taskdef/api-gateway-service.json | 49 + qa-taskdef/cloud-wallet-service.json | 43 + qa-taskdef/connection-service.json | 43 + qa-taskdef/ecosystem-service.json | 43 + qa-taskdef/issuance-service.json | 43 + qa-taskdef/ledger-service.json | 43 + qa-taskdef/notification-service.json | 43 + qa-taskdef/notification-webhook-service.json | 43 + qa-taskdef/organization-service.json | 43 + qa-taskdef/seed.json | 43 + qa-taskdef/user-service.json | 43 + qa-taskdef/utility-service.json | 43 + qa-taskdef/verification-service.json | 43 + qa-taskdef/webhook-service.json | 43 + scripts/taskdef/ecosystem-taskdef.json | 43 + scripts/taskdef/webhook-taskdef.json | 2 +- serviceconnect.json | 55 + stage-taskdef/agent-provisioning.json | 54 + stage-taskdef/agent-service.json | 43 + stage-taskdef/api-gateway-service.json | 49 + stage-taskdef/cloud-wallet-service.json | 43 + stage-taskdef/connection-service.json | 43 + stage-taskdef/ecosystem-service.json | 43 + stage-taskdef/issuance-service.json | 43 + stage-taskdef/ledger-service.json | 43 + stage-taskdef/notification-service.json | 43 + .../notification-webhook-service.json | 43 + stage-taskdef/organization-service.json | 43 + stage-taskdef/seed.json | 43 + stage-taskdef/user-service.json | 43 + stage-taskdef/utility-service.json | 43 + stage-taskdef/verification-service.json | 43 + stage-taskdef/webhook-service.json | 43 + taskdef/agent-provisioning.json | 54 + taskdef/agent-service.json | 43 + taskdef/api-gateway-service.json | 49 + taskdef/connection-service.json | 43 + taskdef/ecosystem-service.json | 43 + taskdef/issuance-service.json | 43 + taskdef/ledger-service.json | 43 + taskdef/notification-service.json | 43 + taskdef/notification-webhook-service.json | 43 + taskdef/organization-service.json | 43 + taskdef/user-service.json | 43 + taskdef/utility-service.json | 43 + taskdef/verification-service.json | 43 + taskdef/webhook-service.json | 43 + tsconfig.build.json | 15 +- tsconfig.json | 41 +- yarn.lock | 8983 ++++++ 569 files changed, 70291 insertions(+), 26118 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .env.demo delete mode 100644 .github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/dco.yml delete mode 100644 .github/workflows/continuous-delivery.yml create mode 100644 .github/workflows/dev-agent-provisioning.yml create mode 100644 .github/workflows/dev-agent.yml create mode 100644 .github/workflows/dev-api-gateway.yml create mode 100644 .github/workflows/dev-connection.yml create mode 100644 .github/workflows/dev-ecosystem.yml create mode 100644 .github/workflows/dev-issuance.yml create mode 100644 .github/workflows/dev-ledger.yml create mode 100644 .github/workflows/dev-notification-webhook.yml create mode 100644 .github/workflows/dev-notification.yml create mode 100644 .github/workflows/dev-organization.yml create mode 100644 .github/workflows/dev-user.yml create mode 100644 .github/workflows/dev-utility.yml create mode 100644 .github/workflows/dev-verification.yml create mode 100644 .github/workflows/dev-webhook.yml create mode 100644 .github/workflows/prod-agent-provisioning.yml create mode 100644 .github/workflows/prod-agent.yml create mode 100644 .github/workflows/prod-api-gateway.yml create mode 100644 .github/workflows/prod-cloud-wallet.yml create mode 100644 .github/workflows/prod-connection.yml create mode 100644 .github/workflows/prod-ecosystem.yml create mode 100644 .github/workflows/prod-issuance.yml create mode 100644 .github/workflows/prod-ledger.yml create mode 100644 .github/workflows/prod-notification.yml create mode 100644 .github/workflows/prod-organization.yml create mode 100644 .github/workflows/prod-seed.yml create mode 100644 .github/workflows/prod-user.yml create mode 100644 .github/workflows/prod-utility.yml create mode 100644 .github/workflows/prod-verification.yml create mode 100644 .github/workflows/prod-webhook.yml create mode 100644 .github/workflows/qa-agent-provisioning.yml create mode 100644 .github/workflows/qa-agent.yml create mode 100644 .github/workflows/qa-api-gateway.yml create mode 100644 .github/workflows/qa-cloud-wallet.yml create mode 100644 .github/workflows/qa-connection.yml create mode 100644 .github/workflows/qa-ecosystem.yml create mode 100644 .github/workflows/qa-issuance.yml create mode 100644 .github/workflows/qa-ledger.yml create mode 100644 .github/workflows/qa-notification.yml create mode 100644 .github/workflows/qa-organization.yml create mode 100644 .github/workflows/qa-seed.yml create mode 100644 .github/workflows/qa-user.yml create mode 100644 .github/workflows/qa-utility.yml create mode 100644 .github/workflows/qa-verification.yml create mode 100644 .github/workflows/qa-webhook.yml delete mode 100644 .github/workflows/scorecard.yml create mode 100644 .github/workflows/stage-agent-provisioning.yml create mode 100644 .github/workflows/stage-agent.yml create mode 100644 .github/workflows/stage-api-gateway.yml create mode 100644 .github/workflows/stage-cloud-wallet.yml create mode 100644 .github/workflows/stage-connection.yml create mode 100644 .github/workflows/stage-ecosystem.yml create mode 100644 .github/workflows/stage-issuance.yml create mode 100644 .github/workflows/stage-ledger.yml create mode 100644 .github/workflows/stage-notification.yml create mode 100644 .github/workflows/stage-organization.yml create mode 100644 .github/workflows/stage-seed.yml create mode 100644 .github/workflows/stage-user.yml create mode 100644 .github/workflows/stage-utility.yml create mode 100644 .github/workflows/stage-verification.yml create mode 100644 .github/workflows/stage-webhook.yml delete mode 100755 .husky/commit-msg delete mode 100755 .husky/post-commit delete mode 100755 .husky/pre-push create mode 100644 .nvmrc create mode 100644 Dockerfiles/Dockerfile.ecosystem delete mode 100644 agent.env create mode 100644 apps/agent-provisioning/AFJ/docker-compose_160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.yaml create mode 100644 apps/agent-provisioning/AFJ/docker-compose_4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin.yaml delete mode 100644 apps/api-gateway/src/authz/decorators/user-auth-client.ts delete mode 100644 apps/api-gateway/src/authz/dtos/user-logout.dto.ts create mode 100644 apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts delete mode 100644 apps/api-gateway/src/authz/guards/session.guard.ts create mode 100644 apps/api-gateway/src/cloud-wallet/dtos/accept-proof-request-with-cred.dto.ts create mode 100644 apps/api-gateway/src/cloud-wallet/dtos/decline-proof-request.dto.ts create mode 100644 apps/api-gateway/src/cloud-wallet/dtos/self-attested-credential.dto.ts delete mode 100644 apps/api-gateway/src/dtos/geo-location-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/add-organizations.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.controller.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.module.ts create mode 100644 apps/api-gateway/src/ecosystem/ecosystem.service.ts delete mode 100644 apps/api-gateway/src/organization/dtos/client-token.dto.ts delete mode 100644 apps/api-gateway/src/schema/dtos/update-schema-dto.ts delete mode 100644 apps/api-gateway/src/tracer.ts create mode 100644 apps/api-gateway/src/user/dto/share-certificate.dto.ts delete mode 100644 apps/api-gateway/src/user/utils/index.ts create mode 100644 apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts create mode 100644 apps/ecosystem/dtos/send-invitation.dto.ts create mode 100644 apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts create mode 100644 apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts create mode 100644 apps/ecosystem/enums/ecosystem.enum.ts create mode 100644 apps/ecosystem/interfaces/ecosystem.interfaces.ts create mode 100644 apps/ecosystem/interfaces/ecosystemMembers.interface.ts create mode 100644 apps/ecosystem/interfaces/endorsements.interface.ts create mode 100644 apps/ecosystem/interfaces/invitations.interface.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.spec.ts create mode 100644 apps/ecosystem/src/ecosystem.controller.ts create mode 100644 apps/ecosystem/src/ecosystem.module.ts create mode 100644 apps/ecosystem/src/ecosystem.repository.ts create mode 100644 apps/ecosystem/src/ecosystem.service.ts create mode 100644 apps/ecosystem/src/main.ts create mode 100644 apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts create mode 100644 apps/ecosystem/templates/EcosystemInviteTemplate.ts create mode 100644 apps/ecosystem/test/app.e2e-spec.ts create mode 100644 apps/ecosystem/test/jest-e2e.json create mode 100644 apps/ecosystem/tsconfig.app.json delete mode 100644 apps/issuance/libs/helpers/attributes.extractor.ts delete mode 100644 apps/ledger/libs/helpers/w3c.schema.builder.ts delete mode 100644 apps/organization/dtos/client-token.dto.ts create mode 100644 apps/user/templates/arbiter-template.ts create mode 100644 apps/user/templates/degree-template.ts create mode 100644 apps/user/templates/event-certificates.ts create mode 100644 apps/user/templates/event-pinnacle.ts create mode 100644 apps/user/templates/participant-template.ts create mode 100644 apps/user/templates/winner-template.ts create mode 100644 apps/user/templates/world-record-template.ts delete mode 100644 compass.yml delete mode 100644 docker-compose-dev.yml delete mode 100644 docker-compose.nats.yml delete mode 100644 docker-compose.redis.yml delete mode 100644 libs/aws/package.json delete mode 100644 libs/aws/tsconfig.build.json delete mode 100644 libs/aws/tsconfig.json rename libs/{config => aws}/tsconfig.lib.json (82%) delete mode 100644 libs/client-registration/src/dtos/client-token.dto.ts delete mode 100644 libs/common/package.json delete mode 100644 libs/common/src/NATSClient.ts create mode 100644 libs/common/src/did.validator.ts delete mode 100644 libs/common/src/email.service.ts create mode 100644 libs/common/src/interfaces/ecosystem.interface.ts delete mode 100644 libs/common/src/nats.interceptor.ts delete mode 100644 libs/common/src/resend-helper-file.ts delete mode 100644 libs/common/src/validator.ts delete mode 100644 libs/common/tsconfig.build.json delete mode 100644 libs/common/tsconfig.json rename libs/{logger => common}/tsconfig.lib.json (82%) delete mode 100644 libs/config/src/config.module.ts delete mode 100644 libs/config/src/config.service.ts delete mode 100644 libs/config/src/global-config.module.ts delete mode 100644 libs/config/src/index.ts delete mode 100644 libs/context/src/contextInterceptorModule.ts delete mode 100644 libs/context/src/contextModule.ts delete mode 100644 libs/context/src/contextStorageService.interface.ts delete mode 100644 libs/context/src/index.ts delete mode 100644 libs/context/src/nestjsClsContextStorageService.ts create mode 100644 libs/entities/base.number.entity.ts create mode 100644 libs/entities/base.system.entity.ts create mode 100644 libs/image-service/src/image-service.module.ts create mode 100644 libs/image-service/src/image-service.service.spec.ts create mode 100644 libs/image-service/src/image-service.service.ts create mode 100644 libs/image-service/src/index.ts create mode 100644 libs/image-service/tsconfig.lib.json delete mode 100644 libs/logger/src/index.ts delete mode 100644 libs/logger/src/log.ts delete mode 100644 libs/logger/src/logger.interface.ts delete mode 100644 libs/logger/src/logger.module.ts delete mode 100644 libs/logger/src/logger.service.ts delete mode 100644 libs/logger/src/logging.interceptor.ts delete mode 100644 libs/logger/src/nestjsLoggerServiceAdapter.ts delete mode 100644 libs/logger/src/transports/consoleTransport.ts delete mode 100644 libs/logger/src/transports/fileTransport.ts delete mode 100644 libs/logger/src/winstonLogger.ts rename libs/prisma-service/prisma/data/{credebl-master-table => }/credebl-master-table.json (60%) delete mode 100644 libs/prisma-service/prisma/migrations/20240919070123_delete_ecosystem_module/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20241111070256_remove_user_credentials_table/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20241114081928_added_is_schema_archived_flag_schema/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20241204160233_add_unique_username/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20241218132640_added_column_in_proof_webhook/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20250106071739_unique_user_id/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20250106094814_removed_unique_email_id/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250305061413_add_column_alias_in_schema_table/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20250416115729_number_of_sub_wallet_per_base_wallet/migration.sql create mode 100644 libs/prisma-service/prisma/migrations/20250611143015_add_user_cascade_delete/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250701025741_added_client_alias/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250721132721_added_relation_for_presentation_with_connection_table/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250724161232_added_connection_for_credentials_with_connection_table/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250822115351_account_session_table_modification/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250829091021_add_expiresat_in_session_table/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250911122855_add_client_info_column_for_sessions_table/migration.sql delete mode 100644 libs/prisma-service/prisma/migrations/20250925060521_added_app_launch_details_column_in_organization/migration.sql create mode 100644 libs/push-notifications/src/index.ts create mode 100644 libs/push-notifications/src/push-notifications.module.ts create mode 100644 libs/push-notifications/src/push-notifications.service.spec.ts create mode 100644 libs/push-notifications/src/push-notifications.service.ts create mode 100644 libs/push-notifications/tsconfig.lib.json create mode 100644 libs/response/src/index.ts create mode 100644 libs/response/src/response.module.ts create mode 100644 libs/response/src/response.service.spec.ts create mode 100644 libs/response/src/response.service.ts rename libs/{context => response}/tsconfig.lib.json (81%) delete mode 100644 libs/validations/arrayKeyword.ts delete mode 100644 libs/validations/exclusiveMaximum.ts delete mode 100644 libs/validations/exclusiveMinimum.ts delete mode 100644 libs/validations/keyword.ts delete mode 100644 libs/validations/maxItems.ts delete mode 100644 libs/validations/maxLength.ts delete mode 100644 libs/validations/maximum.ts delete mode 100644 libs/validations/minItems.ts delete mode 100644 libs/validations/minLength.ts delete mode 100644 libs/validations/minimum.ts delete mode 100644 libs/validations/multipleOf.ts delete mode 100644 libs/validations/numberKeyword.ts delete mode 100644 libs/validations/objectKeyword.ts delete mode 100644 libs/validations/pattern.ts delete mode 100644 libs/validations/stringKeyword.ts delete mode 100644 libs/validations/uniqueItems.ts create mode 100644 package-lock.json mode change 100644 => 100755 package.json delete mode 100644 pnpm-workspace.yaml create mode 100644 prod-taskdef/agent-provisioning.json create mode 100644 prod-taskdef/agent-service.json create mode 100644 prod-taskdef/api-gateway-service.json create mode 100644 prod-taskdef/cloud-wallet-service.json create mode 100644 prod-taskdef/connection-service.json create mode 100644 prod-taskdef/ecosystem-service.json create mode 100644 prod-taskdef/issuance-service.json create mode 100644 prod-taskdef/ledger-service.json create mode 100644 prod-taskdef/notification-service.json create mode 100644 prod-taskdef/notification-webhook-service.json create mode 100644 prod-taskdef/organization-service.json create mode 100644 prod-taskdef/seed.json create mode 100644 prod-taskdef/user-service.json create mode 100644 prod-taskdef/utility-service.json create mode 100644 prod-taskdef/verification-service.json create mode 100644 prod-taskdef/webhook-service.json create mode 100644 qa-taskdef/agent-provisioning.json create mode 100644 qa-taskdef/agent-service.json create mode 100644 qa-taskdef/api-gateway-service.json create mode 100644 qa-taskdef/cloud-wallet-service.json create mode 100644 qa-taskdef/connection-service.json create mode 100644 qa-taskdef/ecosystem-service.json create mode 100644 qa-taskdef/issuance-service.json create mode 100644 qa-taskdef/ledger-service.json create mode 100644 qa-taskdef/notification-service.json create mode 100644 qa-taskdef/notification-webhook-service.json create mode 100644 qa-taskdef/organization-service.json create mode 100644 qa-taskdef/seed.json create mode 100644 qa-taskdef/user-service.json create mode 100644 qa-taskdef/utility-service.json create mode 100644 qa-taskdef/verification-service.json create mode 100644 qa-taskdef/webhook-service.json create mode 100644 scripts/taskdef/ecosystem-taskdef.json create mode 100644 serviceconnect.json create mode 100644 stage-taskdef/agent-provisioning.json create mode 100644 stage-taskdef/agent-service.json create mode 100644 stage-taskdef/api-gateway-service.json create mode 100644 stage-taskdef/cloud-wallet-service.json create mode 100644 stage-taskdef/connection-service.json create mode 100644 stage-taskdef/ecosystem-service.json create mode 100644 stage-taskdef/issuance-service.json create mode 100644 stage-taskdef/ledger-service.json create mode 100644 stage-taskdef/notification-service.json create mode 100644 stage-taskdef/notification-webhook-service.json create mode 100644 stage-taskdef/organization-service.json create mode 100644 stage-taskdef/seed.json create mode 100644 stage-taskdef/user-service.json create mode 100644 stage-taskdef/utility-service.json create mode 100644 stage-taskdef/verification-service.json create mode 100644 stage-taskdef/webhook-service.json create mode 100644 taskdef/agent-provisioning.json create mode 100644 taskdef/agent-service.json create mode 100644 taskdef/api-gateway-service.json create mode 100644 taskdef/connection-service.json create mode 100644 taskdef/ecosystem-service.json create mode 100644 taskdef/issuance-service.json create mode 100644 taskdef/ledger-service.json create mode 100644 taskdef/notification-service.json create mode 100644 taskdef/notification-webhook-service.json create mode 100644 taskdef/organization-service.json create mode 100644 taskdef/user-service.json create mode 100644 taskdef/utility-service.json create mode 100644 taskdef/verification-service.json create mode 100644 taskdef/webhook-service.json create mode 100644 yarn.lock diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index b512c09d4..000000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/.env.demo b/.env.demo deleted file mode 100644 index 87c39697c..000000000 --- a/.env.demo +++ /dev/null @@ -1,222 +0,0 @@ -API_GATEWAY_PROTOCOL=http -API_GATEWAY_HOST=0.0.0.0 -API_GATEWAY_PORT=5000 -API_GATEWAY_PROTOCOL_SECURE=http -API_ENDPOINT=your-ip:5000 - -FRONT_END_URL=http://localhost:3001 - -MOBILE_APP=ADEYA -MOBILE_APP_NAME=ADEYA SSI App -MOBILE_APP_DOWNLOAD_URL='https://blockster.global/products/adeya' -PLAY_STORE_DOWNLOAD_LINK=https://play.google.com/store/apps/details?id=id.credebl.adeya&pli=1 -IOS_DOWNLOAD_LINK=https://apps.apple.com/in/app/adeya-ssi-wallet/id6463845498 - -PLATFORM_NAME=CREDEBL -POWERED_BY=Blockster Labs Pvt. Ltd. -PLATFORM_WEB_URL=https://credebl.id/ -POWERED_BY_URL=https://blockster.global -UPLOAD_LOGO_HOST=devapi.credebl.id -BRAND_LOGO=https://credebl.id/images/CREDEBL_LOGO.png -PLATFORM_ADMIN_EMAIL=platform.admin@yopmail.com - -SOCKET_HOST=ws://your-ip:5000 - -NATS_HOST=your-ip -NATS_PORT=4222 -NATS_URL=nats://your-ip:4222 - -REDIS_HOST=your-ip -REDIS_PORT=6379 - -SENDGRID_API_KEY= - -WALLET_STORAGE_HOST=your-ip -WALLET_STORAGE_PORT=5432 -WALLET_STORAGE_USER='postgres' -WALLET_STORAGE_PASSWORD='postgres' - -CRYPTO_PRIVATE_KEY=dzIvVU5uMa0R3sYwdjEEuT4id17mPpjr -PLATFORM_URL=https://devapi.credebl.id -PLATFORM_PROFILE_MODE=DEV - -PUBLIC_LOCALHOST_URL=http://localhost:5000 -PUBLIC_DEV_API_URL=https://devapi.credebl.id -PUBLIC_QA_API_URL=https://qa-api.credebl.id -PUBLIC_PRODUCTION_API_URL=https://api.credebl.id -PUBLIC_SANDBOX_API_URL=https://sandboxapi.credebl.id -PUBLIC_PLATFORM_SUPPORT_EMAIL=support@blockster.global - -AFJ_VERSION=ghcr.io/credebl/credo-controller:latest - -PLATFORM_WALLET_NAME=platform-admin -PLATFORM_WALLET_PASSWORD='U2FsdGVkX19l6w/PpuicnGBYThBHolzF27oN0JwfWkc=' -PLATFORM_SEED=000000000000000000000000Steward1 -PLATFORM_ID=1 - -# The format for below is as follows: postgresql://{postgres.user}:{postgres.password}@{your-ip}:{postgres.port}/{database-name} -POOL_DATABASE_URL="postgresql://postgres:postgres@your-ip:5432/credebl" -DATABASE_URL="postgresql://postgres:postgres@your-ip:5432/credebl" - -# Used for Bulk issuance of credential -# Optional (Can be skipped if Bulk issuance is not used) -AWS_ACCESS_KEY= -AWS_SECRET_KEY= -AWS_REGION= -AWS_BUCKET= - -# Used for Adding org-logo during org creation and update -# Optional (Can be skipped if no image is added during org creation and updation) -AWS_PUBLIC_ACCESS_KEY= -AWS_PUBLIC_SECRET_KEY= -AWS_PUBLIC_REGION= -AWS_ORG_LOGO_BUCKET_NAME= - -# Used for storing connection URL generated from Agent and creating shortened URL -# Required (As connecting to org requires Shortened url) -AWS_S3_STOREOBJECT_ACCESS_KEY= -AWS_S3_STOREOBJECT_SECRET_KEY= -AWS_S3_STOREOBJECT_REGION= -AWS_S3_STOREOBJECT_BUCKET= - -# Please refere AWS to determine your bucket url -# https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access -SHORTENED_URL_DOMAIN='https://AWS_S3_STOREOBJECT_REGION.amazonaws.com/AWS_S3_STOREOBJECT_BUCKET' -DEEPLINK_DOMAIN='https://link.credebl.id?url=' - -ENABLE_CORS_IP_LIST=http://localhost:3000,http://localhost:3001,http://localhost:5000,http://localhost:8085 - -USER_NKEY_SEED= -API_GATEWAY_NKEY_SEED= -ORGANIZATION_NKEY_SEED= -AGENT_PROVISIONING_NKEY_SEED= -AGENT_SERVICE_NKEY_SEED= -VERIFICATION_NKEY_SEED= -LEDGER_NKEY_SEED= -ISSUANCE_NKEY_SEED= -CONNECTION_NKEY_SEED= -ECOSYSTEM_NKEY_SEED= -CREDENTAILDEFINITION_NKEY_SEED= -SCHEMA_NKEY_SEED= -UTILITIES_NKEY_SEED= -CLOUD_WALLET_NKEY_SEED= -GEOLOCATION_NKEY_SEED= -NOTIFICATION_NKEY_SEED= - -KEYCLOAK_DOMAIN=http://localhost:8080/ -KEYCLOAK_ADMIN_URL=http://localhost:8080 -KEYCLOAK_MASTER_REALM=master -KEYCLOAK_MANAGEMENT_CLIENT_ID=adminClient -KEYCLOAK_MANAGEMENT_CLIENT_SECRET= -KEYCLOAK_REALM=credebl-platform - -SCHEMA_FILE_SERVER_URL= -SCHEMA_FILE_SERVER_TOKEN= - -GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT=/prisma/scripts/geo_location_data_import.sh -UPDATE_CLIENT_CREDENTIAL_SCRIPT=/prisma/scripts/update_client_credential_data.sh -# Note: the below 3 variables are only in case of starting services using docker -AFJ_AGENT_TOKEN_PATH=/agent-provisioning/AFJ/token/ -AFJ_AGENT_SPIN_UP=/agent-provisioning/AFJ/scripts/docker_start_agent.sh -AFJ_AGENT_ENDPOINT_PATH=/agent-provisioning/AFJ/endpoints/ -# Uncomment bellow three lines and comment the above to start services locally without using docker, using pnpm -# AFJ_AGENT_TOKEN_PATH=/apps/agent-provisioning/AFJ/token/ -# AFJ_AGENT_SPIN_UP=/apps/agent-provisioning/AFJ/scripts/start_agent.sh -# AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ - -AGENT_PROTOCOL=http -OOB_BATCH_SIZE=10 -PROOF_REQ_CONN_LIMIT=10 -MAX_ORG_LIMIT=10 -FIDO_API_ENDPOINT=http://localhost:8000 - -IS_ECOSYSTEM_ENABLE=false -CONSOLE_LOG_FLAG=true # Enable or disable console ELK logs -ELK_LOG=false # ELK flag -LOG_LEVEL=debug # ELK log level -ELK_LOG_PATH= "http://localhost:9200/" # ELK log path -ELK_USERNAME=elastic # ELK user username -ELK_PASSWORD=xxxxxx # ELK user password - -ORGANIZATION=credebl -CONTEXT=platform -APP=api - -#Schema-file-server -APP_PORT=4000 -JWT_TOKEN_SECRET= -ISSUER=Credebl - -#Signoz and OTel -IS_ENABLE_OTEL=false -OTEL_SERVICE_NAME='CREDEBL-PLATFORM-SERVICE' -OTEL_SERVICE_VERSION='1.0.0' -OTEL_TRACES_OTLP_ENDPOINT='http://localhost:4318/v1/traces' -OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs' -OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX -OTEL_LOGGER_NAME='credebl-platform-logger' -HOSTNAME='localhost' -SESSIONS_LIMIT=10 -# SSO -APP_PROTOCOL=http -#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name. -CREDEBL_CLIENT_ALIAS=CREDEBL -CREDEBL_DOMAIN=http://localhost:3000 -CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. -CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. -# To add more clients, simply add comma separated values of client names -SUPPORTED_SSO_CLIENTS=CREDEBL - -# Key for agent base wallet -AGENT_API_KEY='supersecret-that-too-16chars' - -ECS_SECURITY_GROUP_ID=sg-xxxxxxxxxxxxxxxxx -FILESYSTEMID=fs-xxxxxxxx -ECS_SUBNET_ID=subnet-xxxxxxxx -INBOUND_TG_ARN=arn:aws:elasticloadbalancing:::targetgroup// -ADMIN_TG_ARN=arn:aws:elasticloadbalancing:::targetgroup// - -CLUSTER_NAME=CREDO-CONTROLLER-CLUSTER # ECS cluster name for credo controller -TASKDEFINITION_FAMILY=CREDO-CONTROLLER-TASKDEFINITION # ECS taskdefinition name for credo controller -PROTOCOL=http -AWS_ACCOUNT_ID=xxxxx // Please provide your AWS account Id -S3_BUCKET_ARN=arn:aws:s3:::xxxxx // Please provide your AWS bucket arn - -# To add more client add the following variables for each additional client. -# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS` -# Default client will not need the following details - -# CLIENT-NAME_CLIENT_ALIAS=VERIFIER - # # Domain represents the redirection url once the client logs-in - # # TODO: Can be taken from keycloak instead -# CLIENT-NAME_DOMAIN=https://VERIFIER-domain.com - # # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY` -# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID= -# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= - -# Sample values: -# VERIFIER_CLIENT_ALIAS=VERIFIER -# VERIFIER_DOMAIN=https://VERIFIER-domain.com -# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId -# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret - -# Active email provider: choose one of [resend | sendgrid | ses | smtp] -EMAIL_PROVIDER=resend - -# Your Resend API key. This is required if EMAIL_PROVIDER=resend -RESEND_API_KEY=re_xxxxxxxxxx -# RESEND_API_KEY=re_xxxxxxxxxx - -# SENDGRID (Fallback option) -# SENDGRID_API_KEY=SG.xxxxxxx - -# if EMAIL_PROVIDER=resend -# AWS_SES_REGION=ap-south-1 -# AWS_SES_ACCESS_KEY=xxxx -# AWS_SES_SECRET_KEY=xxxx - -# if EMAIL_PROVIDER=smtp -# SMTP_HOST=smtp.gmail.com -# SMTP_PORT=587 -# SMTP_USER=your-email@gmail.com -# SMTP_PASS=xxxxxx \ No newline at end of file diff --git a/.env.sample b/.env.sample index 0bd0b5e0a..b5e8522e4 100644 --- a/.env.sample +++ b/.env.sample @@ -98,7 +98,6 @@ PLATFORM_ID= PLATFORM_PROFILE_MODE= // Please provide your environment name OOB_BATCH_SIZE=10 -PROOF_REQ_CONN_LIMIT=10 AFJ_AGENT_ENDPOINT_PATH=/apps/agent-provisioning/AFJ/endpoints/ DATABASE_URL="postgresql://postgres:xxxxxx@localhost:5432/postgres?schema=public" #Provide supabase postgres URL and Use the correct user/pwd, IP Address @@ -117,6 +116,7 @@ AGENT_SERVICE_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for agent VERIFICATION_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for verification service ISSUANCE_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for issuance service CONNECTION_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for connection service +ECOSYSTEM_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for ecosystem service CREDENTAILDEFINITION_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for credential-definition service SCHEMA_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for schema service UTILITIES_NKEY_SEED= xxxxxxxxxxxxx // Please provide Nkeys secret for utilities service @@ -152,90 +152,4 @@ ENABLE_CORS_IP_LIST="" # Provide a list of domains that are allowed to use this SCHEMA_FILE_SERVER_URL= // Please provide schema URL SCHEMA_FILE_SERVER_TOKEN=xxxxxxxx // Please provide schema file server token for polygon -FILEUPLOAD_CACHE_TTL= //Provide file upload cache ttl -SESSIONS_LIMIT= //Provide limits of sessions -FIELD_UPLOAD_SIZE= //Provide field upload size - -IS_ECOSYSTEM_ENABLE= //Set this flag to `true` to enable the ecosystem, or `false` to disable it. -CONSOLE_LOG_FLAG=true // Enable or disable console ELK logs -ELK_LOG=true // ELK flag -LOG_LEVEL=debug // ELK log level -ELK_LOG_PATH = "http://localhost:9200/" // ELK log path -ELK_USERNAME=elastic // ELK user username -ELK_PASSWORD=xxxxxx // ELK user password - -ORGANIZATION=credebl -CONTEXT=platform -APP=api - -IS_ENABLE_OTEL=false # Flag to enable/disable OpenTelemetry (true = enabled, false = disabled) -OTEL_SERVICE_NAME='CREDEBL-PLATFORM-SERVICE' # Logical name of the service shown in observability tools (e.g., SigNoz) -OTEL_SERVICE_VERSION='1.0.0' # Version of the service; helps in tracking changes over time -OTEL_TRACES_OTLP_ENDPOINT='http://localhost:4318/v1/traces' # Endpoint where traces are exported (OTLP over HTTP) -OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs' # Endpoint where logs are exported (OTLP over HTTP) -OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX # API key or token used for authenticating with the OTel collector (e.g., SigNoz) -OTEL_LOGGER_NAME='credebl-platform-logger' # Name of the logger used for OpenTelemetry log records -HOSTNAME='localhost' # Hostname or unique identifier for the service instance - -# SSO -#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name. -CREDEBL_CLIENT_ALIAS=CREDEBL -CREDEBL_DOMAIN=http://localhost:3000 -CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. -CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. -# To add more clients, simply add comma separated values of client names -SUPPORTED_SSO_CLIENTS=CREDEBL -APP_PROTOCOL= - -# Key for agent base wallet -AGENT_API_KEY='supersecret-that-too-16chars' - -PLATFORM_URL=https://devapi.credebl.id -ECS_SECURITY_GROUP_ID=sg-xxxxxxxxxxxxxxxxx -FILESYSTEMID=fs-xxxxxxxx -ECS_SUBNET_ID=subnet-xxxxxxxx -INBOUND_TG_ARN=arn:aws:elasticloadbalancing:::targetgroup// -ADMIN_TG_ARN=arn:aws:elasticloadbalancing:::targetgroup// - -CLUSTER_NAME=CREDO-CONTROLLER-CLUSTER # ECS cluster name for credo controller -TASKDEFINITION_FAMILY=CREDO-CONTROLLER-TASKDEFINITION # ECS taskdefinition name for credo controller -PROTOCOL=http - -# To add more client add the following variables for each additional client. -# Replace the `CLIENT-NAME` with the appropriate client name as added in `SUPPORTED_SSO_CLIENTS` -# Default client will not need the following details - -# CLIENT-NAME_CLIENT_ALIAS=MYAPP - # # Domain represents the redirection url once the client logs-in - # # TODO: Can be taken from keycloak instead -# CLIENT-NAME_DOMAIN=https://myapp.com - # # Encrypted client credentials using the `CRYPTO_PRIVATE_KEY` -# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_ID= -# CLIENT-NAME_KEYCLOAK_MANAGEMENT_CLIENT_SECRET - -# Sample values: -# VERIFIER_CLIENT_ALIAS=VERIFIER -# VERIFIER_DOMAIN=https://VERIFIER-domain.com -# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_ID=encryptedKeyCloakClientId -# VERIFIER_KEYCLOAK_MANAGEMENT_CLIENT_SECRET=encryptedKeyCloakClientSecret - -# Active email provider: choose one of [resend | sendgrid | ses | smtp] -EMAIL_PROVIDER=resend - -# Your Resend API key. This is required if EMAIL_PROVIDER=resend -RESEND_API_KEY=re_xxxxxxxxxx -# RESEND_API_KEY=re_xxxxxxxxxx - -# SENDGRID (Fallback option) -# SENDGRID_API_KEY=SG.xxxxxxx - -# if EMAIL_PROVIDER=resend -# AWS_SES_REGION=ap-south-1 -# AWS_SES_ACCESS_KEY=xxxx -# AWS_SES_SECRET_KEY=xxxx - -# if EMAIL_PROVIDER=smtp -# SMTP_HOST=smtp.gmail.com -# SMTP_PORT=587 -# SMTP_USER=your-email@gmail.com -# SMTP_PASS=xxxxxx \ No newline at end of file +CLOUD_WALLET_COMMON_EMAIL=exampl@domain.com \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 6b733d326..f55a1efc2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -101,4 +101,4 @@ module.exports = { 'prefer-template': 'error', quotes: ['warn', 'single', { allowTemplateLiterals: true }] } -}; +}; \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml deleted file mode 100644 index 4a95540b9..000000000 --- a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: "\U0001F680 Feature Request - new" -title: "feat: " -description: Suggest an idea or enhancement for CREDEBL platform -labels: [enhancement, triage] -body: - - type: markdown - attributes: - value: | - ## Reporting a feature request/enhancement to CREDEBL - - Thank you for taking time to create a feature request for CREDEBL, your contribution will help - make the product better for everyone. - - Make sure your issue has a generous description that will help others understand and fix it at earliest. - - - type: checkboxes - id: preliminary-checks - attributes: - label: "✅ Preliminary Checks" - description: "Please confirm the following before submitting." - options: - - label: "I've read the [contibution guide](https://docs.credebl.id/docs/contribute/how-to-contribute) and agree to it" - required: true - - label: "I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) to avoid duplicates." - required: true - - label: "I'm willing to create a PR for this feature. (if applicable)." - - - type: textarea - id: problem-statement - attributes: - label: "🧩 Problem Statement" - description: "Is your feature request related to a problem? Please describe it clearly." - placeholder: "Ex: I'm always frustrated when [...]" - validations: - required: true - - - type: textarea - id: proposed-solution - attributes: - label: "💡 Proposed Solution" - description: "A clear and concise description of what you want to happen." - placeholder: "Ex: It would be great if [...]" - validations: - required: true - - - type: textarea - id: alternatives-considered - attributes: - label: "🔄 Alternatives Considered" - description: "Have you considered any alternative solutions or features?" - placeholder: "Ex: I also thought about [...], but [...]" - validations: - required: false - - - type: textarea - id: additional-context - attributes: - label: "📎 Additional Context" - description: "Add any other context, references, mockups, or screenshots here." - validations: - required: false - - - type: textarea - id: acceptance-criteria - attributes: - label: "✅ Acceptance Criteria" - description: "List specific tasks or outcomes that define when this request is complete." - placeholder: | - - A new endpoint `/v1/...` is added - - Docs updated - - Tests written and passing - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..08cd90e3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Prerequisites** + +**Steps to Reproduce** + +**Current behavior** + +**Expected behavior** + +**Environment** + +**Desktop** + - OS: + - Browser: + - Browser Version: + - CREDEBL Version: + +**Smartphone** + - Device: + - OS: + - ADEYA version: + +**Screenshots or Screen recording** diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index a9f326c77..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: "🐛 Bug Report" -description: Report a bug or unexpected behavior in the project -labels: [bug] -title: "fix: " -body: - - type: markdown - attributes: - value: | - ## Reporting a bug on CREDEBL - - Thank you for taking time to report the bug on CREDEBL, your contribution will help - make the product better for everyone. - - Make sure your issue has a generous description that will help others understand and fix it at earliest. - - type: checkboxes - id: agreement - attributes: - label: Preliminary Checks - options: - - label: I have read the contributions guide [contibution guide](https://docs.credebl.id/docs/contribute/how-to-contribute) and agree to it - required: true - - label: I have searched [existing issues](https://github.com/credebl/platform/issues) and [pull requests](https://github.com/credebl/platform/pulls) to avoid duplicates. - required: true - - label: "I'm willing to create a PR for this feature. (if applicable)." - - type: textarea - id: steps-to-reproduce - attributes: - label: "🧪 Steps to Reproduce" - description: "Provide clear steps to reproduce the bug." - placeholder: | - 1. Go to '...' - 2. Click on '...' - 3. Scroll down to '...' - 4. See error - validations: - required: true - - - type: textarea - id: expected-behavior - attributes: - label: "✅ Expected Behavior" - description: "What did you expect to happen?" - placeholder: | - Ex: After clicking 'Submit', I expected a confirmation modal to appear. - validations: - required: true - - - type: textarea - id: current-behavior - attributes: - label: "❌ Current Behavior" - description: "What is currently happening instead?" - placeholder: | - Ex: The page crashed with a 500 error when clicking 'Submit'. - validations: - required: true - - - type: input - id: affected-version - attributes: - label: "📌 Affected Version/Commit" - description: "Version number, branch name, or commit hash where the bug occurs." - placeholder: "e.g., v1.2.3, main, 4f3e2d1" - validations: - required: false - - - type: checkboxes - id: environment - attributes: - label: "💻 Environment" - description: "Where did the issue occur?" - options: - - label: "Local development" - - label: "Production" - - label: "CI/CD" - - label: "Other" - - - type: textarea - id: logs-and-screenshots - attributes: - label: "🧾 Relevant Logs, Screenshots, or Stack Traces" - description: "Paste any error messages or screenshots that can help diagnose the issue." - placeholder: | - Please include: - - Error logs - - Screenshots - - Console output - - Stack traces - validations: - required: false diff --git a/.github/dco.yml b/.github/dco.yml deleted file mode 100644 index 1f94d940b..000000000 --- a/.github/dco.yml +++ /dev/null @@ -1,3 +0,0 @@ -allowRemediationCommits: - individual: true - thirdParty: true diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml deleted file mode 100644 index 8bc6c7bb0..000000000 --- a/.github/workflows/continuous-delivery.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Continuous Delivery - -on: - push: - tags: - - 'v*' - -env: - REGISTRY: ghcr.io - -jobs: - build-and-push: - name: Push Docker image to GitHub - runs-on: ubuntu-latest - - strategy: - matrix: - service: - - agent-provisioning - - agent-service - - api-gateway - - cloud-wallet - - connection - - geolocation - - issuance - - ledger - - notification - - user - - utility - - verification - - webhook - - organization - - seed - - permissions: - contents: read - packages: write - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Extract Git Tag - id: get_tag - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and Push Docker Image ${{ matrix.service }} - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfiles/Dockerfile.${{ matrix.service }} - push: true - platforms: linux/amd64,linux/arm64 - tags: | - ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.service }}:${{ env.TAG }} - ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.service }}:latest \ No newline at end of file diff --git a/.github/workflows/dev-agent-provisioning.yml b/.github/workflows/dev-agent-provisioning.yml new file mode 100644 index 000000000..bedcc71bb --- /dev/null +++ b/.github/workflows/dev-agent-provisioning.yml @@ -0,0 +1,142 @@ +name: Build and deploy AGENT_PROVISIONING app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/agent-provisioning/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-provisioning . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_PROVISIONING_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/agent-provisioning.json + - name: Update Task Definition + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/agent-provisioning.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/agent-provisioning.json) + SERVICE_NAME="${NAME}_service" + echo "SERVICE_NAME: ${SERVICE_NAME}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/agent-provisioning.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/user-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/dev-agent.yml b/.github/workflows/dev-agent.yml new file mode 100644 index 000000000..f7e2fd6ab --- /dev/null +++ b/.github/workflows/dev-agent.yml @@ -0,0 +1,132 @@ +name: Build and deploy AGENT app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/agent-service/**' + workflow_dispatch: + +env: + + ECR_IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-service . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/agent-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/agent-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/agent-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/agent-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/dev-api-gateway.yml b/.github/workflows/dev-api-gateway.yml new file mode 100644 index 000000000..a5785526b --- /dev/null +++ b/.github/workflows/dev-api-gateway.yml @@ -0,0 +1,132 @@ +name: Build and deploy API-GATEWAY app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/api-gateway/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.api-gateway . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=API-GATEWAY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/api-gateway-service.json + cat taskdef/api-gateway-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/api-gateway-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/api-gateway-service.json) + SERVICE_NAME="${NAME}_service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/api-gateway-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-connection.yml b/.github/workflows/dev-connection.yml new file mode 100644 index 000000000..e4218d720 --- /dev/null +++ b/.github/workflows/dev-connection.yml @@ -0,0 +1,128 @@ +name: Build and deploy CONNECTION app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/connection/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.connection . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CONNECTION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/connection-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/connection-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/connection-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/connection-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-ecosystem.yml b/.github/workflows/dev-ecosystem.yml new file mode 100644 index 000000000..89ccd450c --- /dev/null +++ b/.github/workflows/dev-ecosystem.yml @@ -0,0 +1,128 @@ +name: Build and deploy ECOSYSTEM app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/ecosystem/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ecosystem . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ECOSYSTEM_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/ecosystem-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/ecosystem-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/ecosystem-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/ecosystem-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-issuance.yml b/.github/workflows/dev-issuance.yml new file mode 100644 index 000000000..239e35507 --- /dev/null +++ b/.github/workflows/dev-issuance.yml @@ -0,0 +1,128 @@ +name: Build and deploy ISSUANCE app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/issuance/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.issuance . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ISSUANCE_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/issuance-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/issuance-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/issuance-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/issuance-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-ledger.yml b/.github/workflows/dev-ledger.yml new file mode 100644 index 000000000..268c53bba --- /dev/null +++ b/.github/workflows/dev-ledger.yml @@ -0,0 +1,128 @@ +name: Build and deploy LEDGER app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/ledger/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ledger . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=LEDGER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/ledger-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/ledger-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/ledger-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/ledger-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-notification-webhook.yml b/.github/workflows/dev-notification-webhook.yml new file mode 100644 index 000000000..b30f3079b --- /dev/null +++ b/.github/workflows/dev-notification-webhook.yml @@ -0,0 +1,128 @@ +name: Build and deploy Node.js app to ECSsdc + +on: + push: + branches: + - develop + paths: + - 'apps/ar/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "USER_v_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "USER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.user . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=USER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/user-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/user-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-notification.yml b/.github/workflows/dev-notification.yml new file mode 100644 index 000000000..6b7f2009e --- /dev/null +++ b/.github/workflows/dev-notification.yml @@ -0,0 +1,128 @@ +name: Build and deploy NOTIFICATION app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/notification/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.notification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=NOTIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/notification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/notification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/notification-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/notification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-organization.yml b/.github/workflows/dev-organization.yml new file mode 100644 index 000000000..e17fac0f8 --- /dev/null +++ b/.github/workflows/dev-organization.yml @@ -0,0 +1,128 @@ +name: Build and deploy ORGANIZATION app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/organization/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.organization . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ORGANIZATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/organization-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/organization-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/organization-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/organization-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-user.yml b/.github/workflows/dev-user.yml new file mode 100644 index 000000000..a5b2efe9a --- /dev/null +++ b/.github/workflows/dev-user.yml @@ -0,0 +1,128 @@ +name: Build and deploy USER app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/user/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "USER_v_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "USER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.user . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=USER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/user-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/user-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-utility.yml b/.github/workflows/dev-utility.yml new file mode 100644 index 000000000..8c6c06dc3 --- /dev/null +++ b/.github/workflows/dev-utility.yml @@ -0,0 +1,128 @@ +name: Build and deploy UTILITY app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/utility/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.utility . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=UTILITY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/utility-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/utility-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/utility-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/utility-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-verification.yml b/.github/workflows/dev-verification.yml new file mode 100644 index 000000000..edbb09775 --- /dev/null +++ b/.github/workflows/dev-verification.yml @@ -0,0 +1,128 @@ +name: Build and deploy VERIFICATION app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/verification/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.verification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=VERIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/verification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/verification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/verification-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/verification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/dev-webhook.yml b/.github/workflows/dev-webhook.yml new file mode 100644 index 000000000..186ce4bb6 --- /dev/null +++ b/.github/workflows/dev-webhook.yml @@ -0,0 +1,128 @@ +name: Build and deploy WEBHOOK app to DEV ECS + +on: + push: + branches: + - develop + paths: + - 'apps/webhook/**' + workflow_dispatch: + +env: + ECR_IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + ECR_REPOSITORY: "dev-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: dev-services + IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.webhook . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=dev-services" >> $GITHUB_ENV + echo "IMAGE_TAG=WEBHOOK_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" taskdef/webhook-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef/webhook-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef/webhook-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef/webhook-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-agent-provisioning.yml b/.github/workflows/prod-agent-provisioning.yml new file mode 100644 index 000000000..1f51f7d10 --- /dev/null +++ b/.github/workflows/prod-agent-provisioning.yml @@ -0,0 +1,141 @@ +name: Build and deploy AGENT_PROVISIONING app to PROD ECS + + +on: + push: + tags: + - 'prod-agent-provisioning*' + +env: + ECR_IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-provisioning . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_PROVISIONING_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/agent-provisioning.json + - name: Update Task Definition + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/agent-provisioning.json) + + SERVICE_NAME="agent-provisioning_service" + echo "SERVICE_NAME: ${SERVICE_NAME}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/agent-provisioning.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/agent-provisioning.json) + + SERVICE_NAME="agent-provisioning-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/agent-provisioning.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/prod-agent.yml b/.github/workflows/prod-agent.yml new file mode 100644 index 000000000..df2228993 --- /dev/null +++ b/.github/workflows/prod-agent.yml @@ -0,0 +1,129 @@ +name: Build and deploy AGENT app to PROD ECS + +on: + push: + tags: + - 'prod-agent-service*' + + +env: + ECR_IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-service . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/agent-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/agent-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/agent-service.json) + SERVICE_NAME="agent_service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/agent-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-api-gateway.yml b/.github/workflows/prod-api-gateway.yml new file mode 100644 index 000000000..df03b928a --- /dev/null +++ b/.github/workflows/prod-api-gateway.yml @@ -0,0 +1,128 @@ +name: Build and deploy API-GATEWAY app to PROD ECS +on: + push: + tags: + - 'prod-api-gateway*' + + +env: + ECR_IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.api-gateway . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=API-GATEWAY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/api-gateway-service.json + cat prod-taskdef/api-gateway-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/api-gateway-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/api-gateway-service.json) + SERVICE_NAME="api_gateway-Service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/api-gateway-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-cloud-wallet.yml b/.github/workflows/prod-cloud-wallet.yml new file mode 100644 index 000000000..026b91230 --- /dev/null +++ b/.github/workflows/prod-cloud-wallet.yml @@ -0,0 +1,137 @@ +name: Build and deploy CLOUD-WALLET app to PROD ECS + +on: + push: + tags: + - 'prod-cloud-wallet*' + +env: + ECR_IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.cloud-wallet . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CLOUD-WALLET_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/cloud-wallet-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/cloud-wallet-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/cloud-wallet-service.json) + # Production service name is "cloud-wallet-service" + SERVICE_NAME="cloud-wallet-service" + + echo "Family: $FAMILY" + echo "Container Name: $NAME" + echo "Service Name: $SERVICE_NAME" + echo "Cluster: ${CLUSTER}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/cloud-wallet-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + # Check if the service exists and get service info + SERVICE_COUNT=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1 | jq -r '.services | length') + + if [ "$SERVICE_COUNT" -eq "0" ]; then + echo "Service ${SERVICE_NAME} does not exist in cluster ${CLUSTER}." + echo "Please create the service manually first, then re-run the deployment." + exit 1 + else + echo "Service exists, updating existing service..." + # Extract desired count from the service info + DESIRED_COUNT=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1 | jq -r '.services[0].desiredCount') + + # Ensure desired count is at least 1 + if [ "$DESIRED_COUNT" = "0" ] || [ "$DESIRED_COUNT" = "null" ] || [ -z "$DESIRED_COUNT" ]; then + DESIRED_COUNT="1" + fi + + echo "Current desired count: $DESIRED_COUNT" + + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + echo "Updating service ${SERVICE_NAME} with task definition ${FAMILY}:${REVISION} and desired count ${DESIRED_COUNT}" + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/prod-connection.yml b/.github/workflows/prod-connection.yml new file mode 100644 index 000000000..54a7a05a3 --- /dev/null +++ b/.github/workflows/prod-connection.yml @@ -0,0 +1,125 @@ +name: Build and deploy CONNECTION app to PROD ECS + +on: + push: + tags: + - 'prod-connection*' + + +env: + ECR_IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.connection . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CONNECTION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/connection-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/connection-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/connection-service.json) + SERVICE_NAME="connection-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/connection-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-ecosystem.yml b/.github/workflows/prod-ecosystem.yml new file mode 100644 index 000000000..ad7c0068d --- /dev/null +++ b/.github/workflows/prod-ecosystem.yml @@ -0,0 +1,125 @@ +name: Build and deploy ECOSYSTEM app to PROD ECS + +on: + push: + tags: + - 'prod-ecosystem*' + + +env: + ECR_IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ecosystem . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ECOSYSTEM_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/ecosystem-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/ecosystem-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/ecosystem-service.json) + SERVICE_NAME="ecosystem-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/ecosystem-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-issuance.yml b/.github/workflows/prod-issuance.yml new file mode 100644 index 000000000..4b283d04e --- /dev/null +++ b/.github/workflows/prod-issuance.yml @@ -0,0 +1,125 @@ +name: Build and deploy ISSUANCE app to PROD ECS + +on: + push: + tags: + - 'prod-issuance*' + + +env: + ECR_IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.issuance . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ISSUANCE_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/issuance-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/issuance-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/issuance-service.json) + SERVICE_NAME="issuance-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/issuance-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-ledger.yml b/.github/workflows/prod-ledger.yml new file mode 100644 index 000000000..5871ad65e --- /dev/null +++ b/.github/workflows/prod-ledger.yml @@ -0,0 +1,125 @@ +name: Build and deploy LEDGER app to PROD ECS + +on: + push: + tags: + - 'prod-ledger*' + + +env: + ECR_IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ledger . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=LEDGER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/ledger-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/ledger-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/ledger-service.json) + SERVICE_NAME="ledger-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/ledger-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-notification.yml b/.github/workflows/prod-notification.yml new file mode 100644 index 000000000..48ddde7a6 --- /dev/null +++ b/.github/workflows/prod-notification.yml @@ -0,0 +1,125 @@ +name: Build and deploy NOTIFICATION app to PROD ECS + +on: + push: + tags: + - 'prod-notification*' + + +env: + ECR_IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.notification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=NOTIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/notification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/notification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/notification-service.json) + SERVICE_NAME="notification-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/notification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-organization.yml b/.github/workflows/prod-organization.yml new file mode 100644 index 000000000..13027b6ae --- /dev/null +++ b/.github/workflows/prod-organization.yml @@ -0,0 +1,125 @@ +name: Build and deploy ORGANIZATION app to PROD ECS + +on: + push: + tags: + - 'prod-organization*' + + +env: + ECR_IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.organization . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ORGANIZATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/organization-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/organization-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/organization-service.json) + SERVICE_NAME="organization-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/organization-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-seed.yml b/.github/workflows/prod-seed.yml new file mode 100644 index 000000000..ee66b14ab --- /dev/null +++ b/.github/workflows/prod-seed.yml @@ -0,0 +1,140 @@ +name: Build and deploy SEED app to PROD ECS + +on: + push: + tags: + - 'prod-seed*' + +env: + ECR_IMAGE_TAG: "SEED_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "SEED_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.seed . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=SEED_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/seed.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/seed.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/seed.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/seed.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists by counting services returned + SERVICE_COUNT=$(echo "$SERVICE_INFO" | jq '.services | length') + echo "Found ${SERVICE_COUNT} services matching ${SERVICE_NAME}" + + if [ "$SERVICE_COUNT" -eq 0 ]; then + echo "Service ${SERVICE_NAME} does not exist, creating new service..." + # Create the service with basic configuration + aws ecs create-service \ + --cluster ${CLUSTER} \ + --service-name ${SERVICE_NAME} \ + --task-definition ${FAMILY} \ + --desired-count 1 \ + --launch-type FARGATE \ + --network-configuration "awsvpcConfiguration={subnets=[subnet-0123456789abcdef0],securityGroups=[sg-0123456789abcdef0],assignPublicIp=ENABLED}" \ + --region ap-southeast-1 + echo "Service ${SERVICE_NAME} created successfully" + else + echo "Service ${SERVICE_NAME} exists, updating..." + # Extract desired count from the existing service + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[0].desiredCount') + if [ "$DESIRED_COUNT" = "0" ] || [ "$DESIRED_COUNT" = "null" ]; then + DESIRED_COUNT="1" + fi + echo "Current desired count: ${DESIRED_COUNT}" + + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + echo "Updating service with task definition ${FAMILY}:${REVISION}" + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + echo "Service ${SERVICE_NAME} updated successfully" + fi \ No newline at end of file diff --git a/.github/workflows/prod-user.yml b/.github/workflows/prod-user.yml new file mode 100644 index 000000000..a796f4693 --- /dev/null +++ b/.github/workflows/prod-user.yml @@ -0,0 +1,125 @@ +name: Build and deploy USER app to PROD ECS + +on: + push: + tags: + - 'prod-user*' + + +env: + ECR_IMAGE_TAG: "USER_v_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "USER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.user . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=USER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/user-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/user-service.json) + SERVICE_NAME="user-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-utility.yml b/.github/workflows/prod-utility.yml new file mode 100644 index 000000000..f61ad8b25 --- /dev/null +++ b/.github/workflows/prod-utility.yml @@ -0,0 +1,127 @@ +name: Build and deploy UTILITY app to PROD ECS + + +on: + push: + tags: + - 'prod-utility*' + + +env: + ECR_IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.utility . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=UTILITY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/utility-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/utility-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/utility-service.json) + SERVICE_NAME="utility-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/utility-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/prod-verification.yml b/.github/workflows/prod-verification.yml new file mode 100644 index 000000000..d2eff1c49 --- /dev/null +++ b/.github/workflows/prod-verification.yml @@ -0,0 +1,125 @@ +name: Build and deploy VERIFICATION app to PROD ECS + +on: + push: + tags: + - 'prod-verification*' + + +env: + ECR_IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.verification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=VERIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/verification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/verification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/verification-service.json) + SERVICE_NAME="verification-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/verification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/prod-webhook.yml b/.github/workflows/prod-webhook.yml new file mode 100644 index 000000000..9135ea233 --- /dev/null +++ b/.github/workflows/prod-webhook.yml @@ -0,0 +1,125 @@ +name: Build and deploy WEBHOOK app to PROD ECS + +on: + push: + tags: + - 'prod-webhook*' + + +env: + ECR_IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + ECR_REPOSITORY: "prod-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "PROD-BACKEND-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: prod-services + IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.webhook . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=prod-services" >> $GITHUB_ENV + echo "IMAGE_TAG=WEBHOOK_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" prod-taskdef/webhook-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' prod-taskdef/webhook-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' prod-taskdef/webhook-service.json) + SERVICE_NAME="webhook_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" prod-taskdef/webhook-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi \ No newline at end of file diff --git a/.github/workflows/qa-agent-provisioning.yml b/.github/workflows/qa-agent-provisioning.yml new file mode 100644 index 000000000..680947ab8 --- /dev/null +++ b/.github/workflows/qa-agent-provisioning.yml @@ -0,0 +1,141 @@ +name: Build and deploy AGENT_PROVISIONING app to QA ECS + +on: + push: + tags: + - 'qa-agent-provisioning*' + +env: + ECR_IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-provisioning . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_PROVISIONING_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/agent-provisioning.json + + - name: Update Task Definition + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/agent-provisioning.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/agent-provisioning.json) + SERVICE_NAME="${NAME}_service" + echo "SERVICE_NAME: ${SERVICE_NAME}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/agent-provisioning.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/user-service.json) + SERVICE_NAME="${NAME}_service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-agent.yml b/.github/workflows/qa-agent.yml new file mode 100644 index 000000000..d70d057c1 --- /dev/null +++ b/.github/workflows/qa-agent.yml @@ -0,0 +1,129 @@ +name: Build and deploy AGENT app to QA ECS + + +on: + push: + tags: + - 'qa-agent-service*' + +env: + ECR_IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-service . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/agent-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/agent-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/agent-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/agent-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-api-gateway.yml b/.github/workflows/qa-api-gateway.yml new file mode 100644 index 000000000..b7f1e6c10 --- /dev/null +++ b/.github/workflows/qa-api-gateway.yml @@ -0,0 +1,129 @@ +name: Build and deploy API-GATEWAY app to QA ECS + +on: + push: + tags: + - 'qa-api-gateway*' + +env: + ECR_IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.api-gateway . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=API-GATEWAY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/api-gateway-service.json + cat qa-taskdef/api-gateway-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/api-gateway-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/api-gateway-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/api-gateway-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-cloud-wallet.yml b/.github/workflows/qa-cloud-wallet.yml new file mode 100644 index 000000000..aaec89d12 --- /dev/null +++ b/.github/workflows/qa-cloud-wallet.yml @@ -0,0 +1,124 @@ +name: Build and deploy CLOUD-WALLET app to QA ECS + +on: + push: + tags: + - 'qa-cloud-wallet*' + +env: + ECR_IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.cloud-wallet . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CLOUD-WALLET_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/cloud-wallet-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/cloud-wallet-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/cloud-wallet-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/cloud-wallet-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-connection.yml b/.github/workflows/qa-connection.yml new file mode 100644 index 000000000..4080f600d --- /dev/null +++ b/.github/workflows/qa-connection.yml @@ -0,0 +1,125 @@ +name: Build and deploy CONNECTION app to QA ECS + +on: + push: + tags: + - 'qa-connection*' + +env: + ECR_IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.connection . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CONNECTION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/connection-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/connection-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/connection-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/connection-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-ecosystem.yml b/.github/workflows/qa-ecosystem.yml new file mode 100644 index 000000000..f9caf59ec --- /dev/null +++ b/.github/workflows/qa-ecosystem.yml @@ -0,0 +1,125 @@ +name: Build and deploy ECOSYSTEM app to QA ECS + +on: + push: + tags: + - 'qa-ecosystem*' + +env: + ECR_IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ecosystem . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ECOSYSTEM_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/ecosystem-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/ecosystem-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/ecosystem-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/ecosystem-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-issuance.yml b/.github/workflows/qa-issuance.yml new file mode 100644 index 000000000..c88fd6549 --- /dev/null +++ b/.github/workflows/qa-issuance.yml @@ -0,0 +1,126 @@ +name: Build and deploy ISSUANCE app to QA ECS + + +on: + push: + tags: + - 'qa-issuance*' + +env: + ECR_IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.issuance . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ISSUANCE_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/issuance-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/issuance-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/issuance-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/issuance-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-ledger.yml b/.github/workflows/qa-ledger.yml new file mode 100644 index 000000000..8159571aa --- /dev/null +++ b/.github/workflows/qa-ledger.yml @@ -0,0 +1,125 @@ +name: Build and deploy LEDGER app to QA ECS + +on: + push: + tags: + - 'qa-ledger*' + +env: + ECR_IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ledger . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=LEDGER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/ledger-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/ledger-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/ledger-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/ledger-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-notification.yml b/.github/workflows/qa-notification.yml new file mode 100644 index 000000000..5f219b0a4 --- /dev/null +++ b/.github/workflows/qa-notification.yml @@ -0,0 +1,125 @@ +name: Build and deploy NOTIFICATION app to QA ECS + +on: + push: + tags: + - 'qa-notification*' + +env: + ECR_IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "DEV-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.notification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=NOTIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/notification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/notification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/notification-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/notification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-organization.yml b/.github/workflows/qa-organization.yml new file mode 100644 index 000000000..d11ba823a --- /dev/null +++ b/.github/workflows/qa-organization.yml @@ -0,0 +1,125 @@ +name: Build and deploy ORGANIZATION app to QA ECS + +on: + push: + tags: + - 'qa-organization*' + +env: + ECR_IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.organization . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ORGANIZATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/organization-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/organization-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/organization-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/organization-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-seed.yml b/.github/workflows/qa-seed.yml new file mode 100644 index 000000000..c04c8c75a --- /dev/null +++ b/.github/workflows/qa-seed.yml @@ -0,0 +1,125 @@ +name: Build and deploy LEDGER app to QA ECS + +on: + push: + tags: + - 'qa-seed*' + +env: + ECR_IMAGE_TAG: "SEED_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "SEED_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.seed . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=SEED_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/seed.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/seed.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/seed.json) + SERVICE_NAME="${NAME}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/seed.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-user.yml b/.github/workflows/qa-user.yml new file mode 100644 index 000000000..a910deb9f --- /dev/null +++ b/.github/workflows/qa-user.yml @@ -0,0 +1,125 @@ +name: Build and deploy USER app to QA ECS + +on: + push: + tags: + - 'qa-user*' + +env: + ECR_IMAGE_TAG: "USER_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "USER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.user . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=USER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/user-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/user-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-utility.yml b/.github/workflows/qa-utility.yml new file mode 100644 index 000000000..7dc4f2b3d --- /dev/null +++ b/.github/workflows/qa-utility.yml @@ -0,0 +1,125 @@ +name: Build and deploy UTILITY app to QA ECS + +on: + push: + tags: + - 'qa-utility*' + +env: + ECR_IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.utility . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=UTILITY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/utility-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/utility-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/utility-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/utility-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-verification.yml b/.github/workflows/qa-verification.yml new file mode 100644 index 000000000..2fce45234 --- /dev/null +++ b/.github/workflows/qa-verification.yml @@ -0,0 +1,125 @@ +name: Build and deploy VERIFICATION app to QA ECS + +on: + push: + tags: + - 'qa-verification*' + +env: + ECR_IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.verification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=VERIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/verification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/verification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/verification-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/verification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/qa-webhook.yml b/.github/workflows/qa-webhook.yml new file mode 100644 index 000000000..ae4a583ae --- /dev/null +++ b/.github/workflows/qa-webhook.yml @@ -0,0 +1,125 @@ +name: Build and deploy WEBHOOK app to QA ECS + +on: + push: + tags: + - 'qa-webhook*' + +env: + ECR_IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + ECR_REPOSITORY: "qa-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "QA-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: qa-services + IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.webhook . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=qa-services" >> $GITHUB_ENV + echo "IMAGE_TAG=WEBHOOK_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" qa-taskdef/webhook-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' qa-taskdef/webhook-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' qa-taskdef/webhook-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" qa-taskdef/webhook-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml deleted file mode 100644 index 971dc081f..000000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,73 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security -on: - # For Branch-Protection check. Only the default branch is supported. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection - branch_protection_rule: - # To guarantee Maintained check is occasionally updated. See - # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained - schedule: - - cron: '29 23 * * 2' - push: - branches: [ "main" ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecard analysis - runs-on: ubuntu-latest - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - # Needed to publish results and get a badge (see publish_results below). - id-token: write - # Uncomment the permissions below if installing in a private repository. - # contents: read - # actions: read - - steps: - - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # - you are installing Scorecard on a *private* repository - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} - - # Public repositories: - # - Publish results to OpenSSF REST API for easy access by consumers - # - Allows the repository to include the Scorecard badge. - # - See https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories: - # - `publish_results` will always be set to `false`, regardless - # of the value entered here. - publish_results: true - - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@v4 - with: - name: sarif-file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard (optional). - # Commenting out will disable upload of results to your repo's Code Scanning dashboard - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/stage-agent-provisioning.yml b/.github/workflows/stage-agent-provisioning.yml new file mode 100644 index 000000000..fefc35198 --- /dev/null +++ b/.github/workflows/stage-agent-provisioning.yml @@ -0,0 +1,139 @@ +name: Build and deploy AGENT_PROVISIONING app to Stage ECS + +on: + push: + tags: + - 'pstage*' + +env: + ECR_IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "AGENT_PROVISIONING_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-provisioning . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_PROVISIONING_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/agent-provisioning.json + - name: Update Task Definition + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/agent-provisioning.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/agent-provisioning.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: ${SERVICE_NAME}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/agent-provisioning.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/user-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-agent.yml b/.github/workflows/stage-agent.yml new file mode 100644 index 000000000..ab63d4756 --- /dev/null +++ b/.github/workflows/stage-agent.yml @@ -0,0 +1,128 @@ +name: Build and deploy AGENT app to Stage ECS + +on: + push: + tags: + - 'stage-agent*' + +env: + ECR_IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "AGENT_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.agent-service . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=AGENT_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/agent-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/agent-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/agent-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/agent-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-api-gateway.yml b/.github/workflows/stage-api-gateway.yml new file mode 100644 index 000000000..3ca1aeaa8 --- /dev/null +++ b/.github/workflows/stage-api-gateway.yml @@ -0,0 +1,129 @@ +name: Build and deploy API-GATEWAY to Stage ECS + +on: + push: + tags: + - 'stage-api-gateway*' + +env: + ECR_IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "API-GATEWAY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.api-gateway . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=API-GATEWAY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/api-gateway-service.json + + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/api-gateway-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/api-gateway-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/api-gateway-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-cloud-wallet.yml b/.github/workflows/stage-cloud-wallet.yml new file mode 100644 index 000000000..6f6b5ada3 --- /dev/null +++ b/.github/workflows/stage-cloud-wallet.yml @@ -0,0 +1,137 @@ +name: Build and deploy CLOUD-WALLET app to Stage ECS + +on: + push: + tags: + - 'stage-cloud-wallet*' + +env: + ECR_IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "CLOUD-WALLET_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.cloud-wallet . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CLOUD-WALLET_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/cloud-wallet-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/cloud-wallet-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/cloud-wallet-service.json) + # Convert hyphen to underscore for service name to match ECS service naming + SERVICE_NAME="${NAME//-/_}-service" + + echo "Family: $FAMILY" + echo "Container Name: $NAME" + echo "Service Name: $SERVICE_NAME" + echo "Cluster: ${CLUSTER}" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/cloud-wallet-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + # Check if the service exists and get service info + SERVICE_COUNT=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1 | jq -r '.services | length') + + if [ "$SERVICE_COUNT" -eq "0" ]; then + echo "Service ${SERVICE_NAME} does not exist in cluster ${CLUSTER}." + echo "Please create the service manually first, then re-run the deployment." + exit 1 + else + echo "Service exists, updating existing service..." + # Extract desired count from the service info + DESIRED_COUNT=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1 | jq -r '.services[0].desiredCount') + + # Ensure desired count is at least 1 + if [ "$DESIRED_COUNT" = "0" ] || [ "$DESIRED_COUNT" = "null" ] || [ -z "$DESIRED_COUNT" ]; then + DESIRED_COUNT="1" + fi + + echo "Current desired count: $DESIRED_COUNT" + + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + echo "Updating service ${SERVICE_NAME} with task definition ${FAMILY}:${REVISION} and desired count ${DESIRED_COUNT}" + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-connection.yml b/.github/workflows/stage-connection.yml new file mode 100644 index 000000000..039ee6fb8 --- /dev/null +++ b/.github/workflows/stage-connection.yml @@ -0,0 +1,128 @@ +name: Build and deploy CONNECTION app to Stage ECS + +on: + push: + tags: + - 'stage-connection*' + +env: + ECR_IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "CONNECTION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.connection . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=CONNECTION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/connection-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/connection-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/connection-service.json) + SERVICE_NAME="${NAME}-service" + echo "SERVICE_NAME: $SERVICE_NAME" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/connection-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + echo "DESIRED_COUNT: $DESIRED_COUNT" + + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-ecosystem.yml b/.github/workflows/stage-ecosystem.yml new file mode 100644 index 000000000..d39b208ab --- /dev/null +++ b/.github/workflows/stage-ecosystem.yml @@ -0,0 +1,125 @@ +name: Build and deploy ECOSYSTEM app to Stage ECS + +on: + push: + tags: + - 'stage-ecosystem*' + +env: + ECR_IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "ECOSYSTEM_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ecosystem . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ECOSYSTEM_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/ecosystem-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/ecosystem-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/ecosystem-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/ecosystem-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-issuance.yml b/.github/workflows/stage-issuance.yml new file mode 100644 index 000000000..f9ede9262 --- /dev/null +++ b/.github/workflows/stage-issuance.yml @@ -0,0 +1,127 @@ +name: Build and deploy ISSUANCE app to Stage ECS + + + +on: + push: + tags: + - 'stage-issuance*' + +env: + ECR_IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "ISSUANCE_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.issuance . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ISSUANCE_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/issuance-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/issuance-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/issuance-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/issuance-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-ledger.yml b/.github/workflows/stage-ledger.yml new file mode 100644 index 000000000..70d3546e5 --- /dev/null +++ b/.github/workflows/stage-ledger.yml @@ -0,0 +1,125 @@ +name: Build and deploy LEDGER app to Stage ECS + +on: + push: + tags: + - 'stage-ledger*' + +env: + ECR_IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "LEDGER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.ledger . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=LEDGER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/ledger-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/ledger-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/ledger-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/ledger-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-notification.yml b/.github/workflows/stage-notification.yml new file mode 100644 index 000000000..ef25123aa --- /dev/null +++ b/.github/workflows/stage-notification.yml @@ -0,0 +1,125 @@ +name: Build and deploy NOTIFICATION app to Stage ECS + +on: + push: + tags: + - 'stage-notification*' + +env: + ECR_IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "NOTIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.notification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=NOTIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/notification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/notification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/notification-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/notification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-organization.yml b/.github/workflows/stage-organization.yml new file mode 100644 index 000000000..07664f0fa --- /dev/null +++ b/.github/workflows/stage-organization.yml @@ -0,0 +1,125 @@ +name: Build and deploy ORGANIZATION app to Stage ECS + +on: + push: + tags: + - 'stage-organization*' + +env: + ECR_IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "ORGANIZATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.organization . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=ORGANIZATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/organization-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/organization-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/organization-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/organization-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-seed.yml b/.github/workflows/stage-seed.yml new file mode 100644 index 000000000..e11997c4c --- /dev/null +++ b/.github/workflows/stage-seed.yml @@ -0,0 +1,125 @@ +name: Build and deploy SEED app to Stage ECS + +on: + push: + tags: + - 'stage-seed*' + +env: + ECR_IMAGE_TAG: "SEED_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "SEED_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.seed . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=SEED_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/seed.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/seed.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/seed.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/seed.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-user.yml b/.github/workflows/stage-user.yml new file mode 100644 index 000000000..5b06667e4 --- /dev/null +++ b/.github/workflows/stage-user.yml @@ -0,0 +1,125 @@ +name: Build and deploy USER app to Stage ECS + +on: + push: + tags: + - 'stage-user*' + +env: + ECR_IMAGE_TAG: "USER_v_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "USER_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.user . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=USER_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/user-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/user-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/user-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/user-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-utility.yml b/.github/workflows/stage-utility.yml new file mode 100644 index 000000000..2e4709fb8 --- /dev/null +++ b/.github/workflows/stage-utility.yml @@ -0,0 +1,125 @@ +name: Build and deploy UTILITY app to Stage ECS + +on: + push: + tags: + - 'stage-utility*' + +env: + ECR_IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "UTILITY_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.utility . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=UTILITY_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/utility-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/utility-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/utility-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/utility-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-verification.yml b/.github/workflows/stage-verification.yml new file mode 100644 index 000000000..cc2592a00 --- /dev/null +++ b/.github/workflows/stage-verification.yml @@ -0,0 +1,126 @@ +name: Build and deploy VERIFICATION app to Stage ECS + +on: + push: + tags: + - 'stage-verification*' + +env: + ECR_IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "VERIFICATION_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.verification . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=VERIFICATION_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/verification-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/verification-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/verification-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/verification-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + DESIRED_COUNT="1" + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.github/workflows/stage-webhook.yml b/.github/workflows/stage-webhook.yml new file mode 100644 index 000000000..b27d2d183 --- /dev/null +++ b/.github/workflows/stage-webhook.yml @@ -0,0 +1,125 @@ +name: Build and deploy WEBHOOK app to Stage ECS + +on: + push: + tags: + - 'stage-webhook*' + +env: + ECR_IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + ECR_REPOSITORY: "stage-services" + AWS_REGION: "ap-southeast-1" + CLUSTER: "STAGE-NGOTAG-CLUSTER" + +jobs: + build: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Check if tag creator is allowed + id: check-user + run: | + ALLOWED_USERS=("Chimi1999" "devdgna") + echo "Tag pushed by: ${{ github.actor }}" + is_allowed="false" + for user in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$user" ]]; then + is_allowed="true" + break + fi + done + echo "is_allowed=$is_allowed" >> $GITHUB_OUTPUT + + - name: Delete tag if unauthorized + if: steps.check-user.outputs.is_allowed != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG_NAME=${GITHUB_REF#refs/tags/} + echo "Unauthorized tag push detected by ${{ github.actor }}. Deleting tag: $TAG_NAME" + curl -X DELETE -H "Authorization: token $GITHUB_TOKEN" \ + https://api.github.com/repos/${{ github.repository }}/git/refs/tags/$TAG_NAME + exit 1 + + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v3 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + aws-region: ap-southeast-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: stage-services + IMAGE_TAG: "WEBHOOK_V_${{ github.run_number }}" + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfiles/Dockerfile.webhook . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker image list + + - name: Set environment variables + run: | + echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV + echo "ECR_REPOSITORY=stage-services" >> $GITHUB_ENV + echo "IMAGE_TAG=WEBHOOK_V_${{ github.run_number }}" >> $GITHUB_ENV + + - name: Print environment variables + run: | + echo "ECR_REGISTRY: $ECR_REGISTRY" + echo "ECR_REPOSITORY: $ECR_REPOSITORY" + echo "IMAGE_TAG: $IMAGE_TAG" + + - name: Retrieve Repository URI + run: | + REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --region ${{ env.AWS_REGION }} | jq -r '.repositories[].repositoryUri') + echo "REPOSITORY_URI=${REPOSITORY_URI}" >> $GITHUB_ENV + + - name: Replace executionRoleArn in task definition + run: | + sed -i "s#\"executionRoleArn\": \"arn:aws:iam::.*:role/ecsTaskExecutionRole\"#\"executionRoleArn\": \"arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/ecsTaskExecutionRole\"#" stage-taskdef/webhook-service.json + + - name: Update Task Definition and service + run: | + FAMILY=$(sed -n 's/.*"family": "\(.*\)",/\1/p' stage-taskdef/webhook-service.json) + NAME=$(sed -n 's/.*"name": "\(.*\)",/\1/p' stage-taskdef/webhook-service.json) + SERVICE_NAME="${NAME}-service" + + # Replace placeholders in the JSON file + sed -e "s;%BUILD_NUMBER%;${{ github.run_number }};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" stage-taskdef/webhook-service.json > ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Debug: Print the content of the modified JSON file + cat ${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json + + # Register the task definition using the modified JSON file + aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${GITHUB_WORKSPACE}/${NAME}-v_${{ github.run_number }}.json --region ${{ env.AWS_REGION }} + + SERVICE_INFO=$(aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ap-southeast-1) + + # Check if the service exists + if [ -z "$SERVICE_INFO" ]; then + echo "Service does not exist, creating new service..." + # Your logic to create a new service goes here + else + echo "Entered existing service" + # Extract desired count from the stored service info + DESIRED_COUNT=$(echo "$SERVICE_INFO" | jq -r '.services[].desiredCount') + if [ "$DESIRED_COUNT" = "0" ]; then + DESIRED_COUNT="1" + fi + # Update the existing service + REVISION=$(aws ecs describe-task-definition --task-definition ${FAMILY} --region ap-southeast-1 | jq -r '.taskDefinition.revision') + aws ecs update-service --cluster ${CLUSTER} --region ap-southeast-1 --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT} + fi diff --git a/.gitignore b/.gitignore index 4b82df9a5..5718a8931 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ dist sonar-project.properties .scannerwork/* coverage -libs/prisma-service/prisma/data/credebl-master-table/credebl-master-table.json +libs/prisma-service/prisma/data/credebl-master-table.json uploadedFles/exports uploadedFles/import uploadedFles/export diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index 5466d5eed..000000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -commit_msg_file=$1 - -# Guard: ensure commit message file exists -if [ -z "$commit_msg_file" ] || [ ! -f "$commit_msg_file" ]; then - echo "⚠️ No commit message file provided or file does not exist." - echo "👉 Skipping DCO sign-off check." - exit 0 -fi - -if ! grep -q "Signed-off-by:" "$commit_msg_file"; then - echo "Commit message missing Signed-off-by line" - echo "Use: git commit -s ..." - exit 1 -fi diff --git a/.husky/post-commit b/.husky/post-commit deleted file mode 100755 index 3fb820718..000000000 --- a/.husky/post-commit +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env sh -RESET="\033[0m" -RED="\033[0;31m" -GREEN="\033[0;32m" -YELLOW="\033[0;33m" -BLUE="\033[0;34m" -CYAN="\033[0;36m" - -. "$(dirname -- "$0")/_/husky.sh" - -echo "${BLUE}Checking last commit signature...${RESET}" - -last_commit=$(git rev-parse HEAD) - -# Count parents to detect merge commits -parent_count=$(git rev-list --parents -n 1 $last_commit | awk '{print NF-1}') - -if [ "$parent_count" -gt 1 ]; then - echo "${BLUE}Skipping merge commit $last_commit${RESET}" - exit 0 -fi - -sig_status=$(git log --format='%G?' -n 1 $last_commit) - -if [ "$sig_status" != "G" ]; then - echo "${RED}❌ Commit $last_commit is not verified!${RESET}" - echo - echo "${YELLOW}👉 How to fix:${RESET}" - echo " 1. Re-sign your last commit with both sign-off (-s) and signature (-S):" - echo " git commit --amend -sS --no-edit" - echo - echo " 2. If you haven't set up commit signing yet:" - echo " - GPG/SSH signing setup: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits" - echo - echo "${CYAN}💡 Tip: You can enable automatic signing for all commits with:${RESET}" - echo " git config --global commit.gpgsign true" - echo - exit 1 - # In post-commit we usually *warn* not block, but you can `exit 1` if you want strict enforcement -else - echo "${GREEN}Commit $last_commit is signed and verified.${RESET}" -fi diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index ccf05aa56..000000000 --- a/.husky/pre-push +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -# ANSI colors -RED="\033[0;31m" -YELLOW="\033[0;33m" -CYAN="\033[0;36m" -GREEN="\033[0;32m" -RESET="\033[0m" - -echo "🔒 Checking commit signatures..." - -while read local_ref local_sha remote_ref remote_sha; do - # Determine range of commits - if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then - range="$local_sha" - else - range="$remote_sha..$local_sha" - fi - - unsigned_commits="" - - # Loop through each commit in range - for commit in $(git rev-list $range); do - # Skip merge commits - parent_count=$(git rev-list --parents -n 1 $commit | awk '{print NF-1}') - if [ "$parent_count" -gt 1 ]; then - echo "${CYAN}ℹ️ Skipping merge commit $commit${RESET}" - continue - fi - sig_status=$(git log --format='%G?' -n 1 $commit) - # Currently only check for "N" - for no signature. - if [ "$sig_status" = "N" ]; then - unsigned_commits="$unsigned_commits $commit" - fi - done - - # If there are unsigned commits, prompt the developer - if [ -n "$unsigned_commits" ]; then - echo "${RED}❌ Found unsigned commits:${RESET} $unsigned_commits" - echo - echo "${YELLOW}Do you want to automatically sign these commits? [y/N]${RESET}" - read -r answer " - echo - exit 1 - fi - fi -done - -echo "${GREEN}✅ No unsigned commits found${RESET}" \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..aacb51810 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.17 diff --git a/Dockerfiles/Dockerfile.agent-provisioning b/Dockerfiles/Dockerfile.agent-provisioning index 2844893d6..9c92f0baf 100644 --- a/Dockerfiles/Dockerfile.agent-provisioning +++ b/Dockerfiles/Dockerfile.agent-provisioning @@ -1,15 +1,12 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk update && apk upgrade -RUN apk add --no-cache openssl + RUN set -eux \ && apk --no-cache add \ openssh-client \ aws-cli \ docker \ docker-compose \ - jq \ && npm install -g pnpm --ignore-scripts \ && export PATH=$PATH:/usr/lib/node_modules/pnpm/bin \ && rm -rf /var/cache/apk/* @@ -19,19 +16,16 @@ WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . # Generate Prisma client # RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate -RUN cd libs/prisma-service && npx prisma generate +RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate RUN ls -R /app/apps/agent-provisioning/AFJ/ # Build the user service @@ -39,16 +33,13 @@ RUN pnpm run build agent-provisioning # Stage 2: Create the final image FROM node:18-alpine as prod -# Install OpenSSL -RUN apk update && apk upgrade -RUN apk add --no-cache openssl + RUN set -eux \ && apk --no-cache add \ openssh-client \ aws-cli \ docker \ docker-compose \ - jq \ && npm install -g pnpm --ignore-scripts \ && export PATH=$PATH:/usr/lib/node_modules/pnpm/bin \ && rm -rf /var/cache/apk/* @@ -71,7 +62,6 @@ COPY --from=build /app/apps/agent-provisioning/AFJ/port-file ./agent-provisionin RUN chmod +x /app/agent-provisioning/AFJ/scripts/start_agent.sh RUN chmod +x /app/agent-provisioning/AFJ/scripts/start_agent_ecs.sh RUN chmod +x /app/agent-provisioning/AFJ/scripts/docker_start_agent.sh -RUN chmod +x /app/agent-provisioning/AFJ/scripts/fargate.sh RUN chmod 777 /app/agent-provisioning/AFJ/endpoints RUN chmod 777 /app/agent-provisioning/AFJ/agent-config RUN chmod 777 /app/agent-provisioning/AFJ/token @@ -80,4 +70,7 @@ RUN chmod 777 /app/agent-provisioning/AFJ/token COPY libs/ ./libs/ # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/agent-provisioning/main.js"] \ No newline at end of file +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && cd ../.. && node dist/apps/agent-provisioning/main.js"] + +# docker build -t agent-provisioning-service -f Dockerfiles/Dockerfile.agent-provisioning . +# docker run -d --env-file .env --name agent-provisioning-service docker.io/library/agent-provisioning-service \ No newline at end of file diff --git a/Dockerfiles/Dockerfile.agent-service b/Dockerfiles/Dockerfile.agent-service index ae85a6770..8cf160c63 100644 --- a/Dockerfiles/Dockerfile.agent-service +++ b/Dockerfiles/Dockerfile.agent-service @@ -1,10 +1,10 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl + RUN npm install -g pnpm --ignore-scripts \ && apk update \ && apk add openssh-client \ + && apk add openssl \ && apk add aws-cli \ && apk add docker \ && apk add docker-compose @@ -15,12 +15,9 @@ WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -32,16 +29,16 @@ RUN pnpm run build agent-service # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl + RUN npm install -g pnpm --ignore-scripts \ && apk update \ && apk add openssh-client \ + && apk add openssl \ && apk add aws-cli \ && apk add docker \ && apk add docker-compose -# RUN npm install -g pnpm +RUN npm install -g pnpm # Set the working directory WORKDIR /app @@ -54,4 +51,4 @@ COPY --from=build /app/libs/ ./libs/ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/agent-service/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/agent-service/main.js"] diff --git a/Dockerfiles/Dockerfile.api-gateway b/Dockerfiles/Dockerfile.api-gateway index 69ac7000f..734be7520 100644 --- a/Dockerfiles/Dockerfile.api-gateway +++ b/Dockerfiles/Dockerfile.api-gateway @@ -1,20 +1,18 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ # COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -26,8 +24,9 @@ RUN pnpm run build api-gateway # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl # Set the working directory WORKDIR /app diff --git a/Dockerfiles/Dockerfile.cloud-wallet b/Dockerfiles/Dockerfile.cloud-wallet index 1c3160b20..cda794010 100644 --- a/Dockerfiles/Dockerfile.cloud-wallet +++ b/Dockerfiles/Dockerfile.cloud-wallet @@ -1,20 +1,17 @@ # Stage 1: Build the application -FROM node:18-alpine AS build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +FROM node:18-slim as build +RUN npm install -g pnpm --ignore-scripts \ + && apt-get update \ + && apt-get install -y openssl # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm install # Copy the rest of the application code COPY . . @@ -25,13 +22,14 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build cloud-wallet # Stage 2: Create the final image -FROM node:18-alpine - -RUN apk add --no-cache openssl +FROM node:18-slim # Set the working directory WORKDIR /app -# RUN npm install -g pnpm + +RUN npm install -g pnpm --ignore-scripts \ + && apt-get update \ + && apt-get install -y openssl # Copy the compiled code from the build stage COPY --from=build /app/dist/apps/cloud-wallet/ ./dist/apps/cloud-wallet/ @@ -43,4 +41,4 @@ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/cloud-wallet/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/cloud-wallet/main.js"] diff --git a/Dockerfiles/Dockerfile.connection b/Dockerfiles/Dockerfile.connection index 0174fe803..81563fb07 100644 --- a/Dockerfiles/Dockerfile.connection +++ b/Dockerfiles/Dockerfile.connection @@ -1,20 +1,18 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -26,9 +24,10 @@ RUN pnpm run build connection # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -44,4 +43,4 @@ COPY --from=build /app/node_modules ./node_modules #RUN npm i --only=production # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/connection/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/connection/main.js"] diff --git a/Dockerfiles/Dockerfile.ecosystem b/Dockerfiles/Dockerfile.ecosystem new file mode 100644 index 000000000..39cb38ba9 --- /dev/null +++ b/Dockerfiles/Dockerfile.ecosystem @@ -0,0 +1,44 @@ +# Stage 1: Build the application +FROM node:18-alpine as build +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package.json ./ +#COPY package-lock.json ./ + +# Install dependencies +RUN pnpm i + +# Copy the rest of the application code +COPY . . +# RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate +RUN cd libs/prisma-service && npx prisma generate + +# Build the issuance service +RUN pnpm run build ecosystem + +# Stage 2: Create the final image +FROM node:18-alpine +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + +# Set the working directory +WORKDIR /app + +# Copy the compiled code from the build stage +COPY --from=build /app/dist/apps/ecosystem/ ./dist/apps/ecosystem/ + +# Copy the libs folder from the build stage +COPY --from=build /app/libs/ ./libs/ +#COPY --from=build /app/package.json ./ +COPY --from=build /app/node_modules ./node_modules + + +# Set the command to run the microservice +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/ecosystem/main.js"] diff --git a/Dockerfiles/Dockerfile.geolocation b/Dockerfiles/Dockerfile.geolocation index 239275448..d29e6de7a 100644 --- a/Dockerfiles/Dockerfile.geolocation +++ b/Dockerfiles/Dockerfile.geolocation @@ -1,20 +1,15 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl RUN npm install -g pnpm # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -26,9 +21,7 @@ RUN pnpm run build geo-location # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm # Set the working directory WORKDIR /app @@ -44,4 +37,4 @@ COPY --from=build /app/node_modules ./node_modules #RUN npm i --only=production # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/geo-location/main.js"] \ No newline at end of file +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/geo-location/main.js"] \ No newline at end of file diff --git a/Dockerfiles/Dockerfile.issuance b/Dockerfiles/Dockerfile.issuance index 11c6cc002..95662e5b4 100644 --- a/Dockerfiles/Dockerfile.issuance +++ b/Dockerfiles/Dockerfile.issuance @@ -1,20 +1,18 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -26,9 +24,10 @@ RUN pnpm run build issuance # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -43,4 +42,4 @@ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/issuance/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/issuance/main.js"] diff --git a/Dockerfiles/Dockerfile.ledger b/Dockerfiles/Dockerfile.ledger index 4be86fc7a..0eec694e9 100644 --- a/Dockerfiles/Dockerfile.ledger +++ b/Dockerfiles/Dockerfile.ledger @@ -1,20 +1,18 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -27,9 +25,10 @@ RUN npm run build ledger # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -42,4 +41,4 @@ COPY --from=build /app/libs/ ./libs/ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/ledger/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/ledger/main.js"] diff --git a/Dockerfiles/Dockerfile.notification b/Dockerfiles/Dockerfile.notification index 718526704..cb8d76945 100644 --- a/Dockerfiles/Dockerfile.notification +++ b/Dockerfiles/Dockerfile.notification @@ -1,14 +1,14 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm --ignore-scripts +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ # Install dependencies @@ -25,9 +25,10 @@ RUN npm run build notification # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm --ignore-scripts +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -40,4 +41,4 @@ COPY --from=build /app/libs/ ./libs/ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/notification/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/notification/main.js"] diff --git a/Dockerfiles/Dockerfile.organization b/Dockerfiles/Dockerfile.organization index 0b1fe5486..768cc6a6a 100644 --- a/Dockerfiles/Dockerfile.organization +++ b/Dockerfiles/Dockerfile.organization @@ -1,34 +1,34 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . # RUN cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate RUN cd libs/prisma-service && npx prisma generate + # Build the organization service RUN pnpm run build organization # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -41,4 +41,4 @@ COPY --from=build /app/libs/ ./libs/ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/organization/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/organization/main.js"] diff --git a/Dockerfiles/Dockerfile.seed b/Dockerfiles/Dockerfile.seed index 8a02b1e4a..933085f21 100644 --- a/Dockerfiles/Dockerfile.seed +++ b/Dockerfiles/Dockerfile.seed @@ -8,16 +8,11 @@ RUN apk add --no-cache postgresql-client openssl # Set working directory WORKDIR /app +# Copy the rest of the source code COPY . . RUN chmod +x /app/libs/prisma-service/prisma/scripts/geo_location_data_import.sh RUN chmod +x /app/libs/prisma-service/prisma/scripts/update_client_credential_data.sh -ENV PUPPETEER_SKIP_DOWNLOAD=true -COPY pnpm-workspace.yaml ./ -RUN pnpm i --ignore-scripts - -# Run Prisma commands -RUN cd libs/prisma-service && npx prisma generate # Set the command to run the microservice CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && npx prisma db seed"] \ No newline at end of file diff --git a/Dockerfiles/Dockerfile.user b/Dockerfiles/Dockerfile.user index 8cc17d320..b0d937fec 100644 --- a/Dockerfiles/Dockerfile.user +++ b/Dockerfiles/Dockerfile.user @@ -1,21 +1,29 @@ # Stage 1: Build the application -FROM node:18-alpine AS build - -# Install OpenSSL -RUN apk add --no-cache openssl +FROM node:18-slim as build RUN npm install -g pnpm +# We don't need the standalone Chromium +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true +ENV PUPPETEER_SKIP_DOWNLOAD true + +# Install Google Chrome Stable and fonts +# Note: this installs the necessary libs to make the browser work with Puppeteer. +RUN apt-get update && apt-get install gnupg wget -y && \ + wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ + sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ + apt-get update && \ + apt-get install google-chrome-stable -y --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* + +# RUN apk update && apk list --all-versions chromium # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true # Install dependencies -RUN pnpm i --ignore-scripts +RUN pnpm install # Copy the rest of the application code COPY . . @@ -26,11 +34,24 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build user # Stage 2: Create the final image -FROM node:18-alpine +FROM node:18-slim + +# We don't need the standalone Chromium +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true +ENV PUPPETEER_SKIP_DOWNLOAD true + +# Install Google Chrome Stable and fonts +# Note: this installs the necessary libs to make the browser work with Puppeteer. +RUN apt-get update && apt-get install gnupg wget -y && \ + wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ + sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ + apt-get update && \ + apt-get install google-chrome-stable -y --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* -RUN apk add --no-cache openssl # Set the working directory WORKDIR /app +RUN npm install -g pnpm # Copy the compiled code from the build stage COPY --from=build /app/dist/apps/user/ ./dist/apps/user/ @@ -42,4 +63,4 @@ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/user/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/user/main.js"] diff --git a/Dockerfiles/Dockerfile.utility b/Dockerfiles/Dockerfile.utility index 797c47867..8d46e8567 100644 --- a/Dockerfiles/Dockerfile.utility +++ b/Dockerfiles/Dockerfile.utility @@ -1,20 +1,16 @@ # Stage 1: Build the application -FROM node:18-alpine AS build - -# Install OpenSSL -RUN apk add --no-cache openssl +FROM node:18-slim as build RUN npm install -g pnpm +RUN apt-get update -y +RUN apt-get --no-install-recommends install -y openssl # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm install # Copy the rest of the application code COPY . . @@ -25,12 +21,13 @@ RUN cd libs/prisma-service && npx prisma generate RUN pnpm run build utility # Stage 2: Create the final image -FROM node:18-alpine +FROM node:18-slim -RUN apk add --no-cache openssl +RUN apt-get update -y +RUN apt-get --no-install-recommends install -y openssl # Set the working directory WORKDIR /app -# RUN npm install -g pnpm +RUN npm install -g pnpm # Copy the compiled code from the build stage COPY --from=build /app/dist/apps/utility/ ./dist/apps/utility/ @@ -42,4 +39,4 @@ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/utility/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/utility/main.js"] diff --git a/Dockerfiles/Dockerfile.verification b/Dockerfiles/Dockerfile.verification index e6780d08b..25ddecc77 100644 --- a/Dockerfiles/Dockerfile.verification +++ b/Dockerfiles/Dockerfile.verification @@ -1,19 +1,17 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm -ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ - -ENV PUPPETEER_SKIP_DOWNLOAD=true -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -25,9 +23,10 @@ RUN npm run build verification # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm -ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -40,4 +39,4 @@ COPY --from=build /app/libs/ ./libs/ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/verification/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/verification/main.js"] diff --git a/Dockerfiles/Dockerfile.webhook b/Dockerfiles/Dockerfile.webhook index e2664d398..0396c5dd3 100644 --- a/Dockerfiles/Dockerfile.webhook +++ b/Dockerfiles/Dockerfile.webhook @@ -1,20 +1,18 @@ # Stage 1: Build the application FROM node:18-alpine as build -# Install OpenSSL -RUN apk add --no-cache openssl -RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app # Copy package.json and package-lock.json COPY package.json ./ -COPY pnpm-workspace.yaml ./ #COPY package-lock.json ./ -ENV PUPPETEER_SKIP_DOWNLOAD=true - -# Install dependencies while ignoring scripts (including Puppeteer's installation) -RUN pnpm i --ignore-scripts +# Install dependencies +RUN pnpm i # Copy the rest of the application code COPY . . @@ -26,9 +24,10 @@ RUN pnpm run build webhook # Stage 2: Create the final image FROM node:18-alpine -# Install OpenSSL -RUN apk add --no-cache openssl -# RUN npm install -g pnpm +RUN npm install -g pnpm --ignore-scripts \ + && apk update \ + && apk add openssl + # Set the working directory WORKDIR /app @@ -43,4 +42,4 @@ COPY --from=build /app/node_modules ./node_modules # Set the command to run the microservice -CMD ["sh", "-c", "cd libs/prisma-service && npx prisma migrate deploy && npx prisma generate && cd ../.. && node dist/apps/webhook/main.js"] +CMD ["sh", "-c", "cd libs/prisma-service && npx prisma generate npx prisma migrate deploy && cd ../.. && node dist/apps/webhook/main.js"] diff --git a/LICENSE b/LICENSE index a74d26298..d98215d8f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2024] [AYANWORKS Technology Solutions Private Limited] + Copyright [2023] [Blockster Labs Private Limited] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 20f15ecff..987d7cad2 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,83 @@ # CREDEBL SSI Platform -This repository hosts the codebase for CREDEBL SSI Platform backend. +This repository host codebase for CREDEBL SSI Platform backend. -## Prerequisites +## Pre-requisites -### • Install Docker and Docker Compose -See: https://docs.docker.com/engine/install/ +Install Docker and docker-compose +
See: https://docs.docker.com/engine/install/ -### • Install Node.js -Version: >= 18.17.0 -See: https://nodejs.dev/en/learn/how-to-install-nodejs/ +Install Node: >= 18.17.0 +
See: https://nodejs.dev/en/learn/how-to-install-nodejs/ -### • Install NestJS CLI +**Install NestJS** ```bash npm i @nestjs/cli@latest ``` -## Setup Instructions - -### • Setup and Run PostgreSQL -Start the PostgreSQL service using Docker: +**Setup & run postgres** +Start the postgresql service using the docker: ```bash -docker run --name credebl-postgres \ - -p 5432:5432 \ - -e POSTGRES_USER=credebl \ - -e POSTGRES_PASSWORD=changeme \ - -e POSTGRES_DB=credebl \ - -v credebl_pgdata:/var/lib/postgresql/data \ - -d postgres:16 +docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD= -e POSTGRES_USER=credebl -d postgres ``` -### • Run Prisma to Generate Database Schema +**Run prisma to generate db schema** ```bash -cd ./libs/prisma-service/prisma +cd ./libs/prisma-servie/prisma npx prisma generate npx prisma db push ``` -### • Seed Initial Data +**Seed initial data** ```bash -cd ./libs/prisma-service +cd ./libs/prisma-servie npx prisma db seed ``` -## Install NATS Message Broker - -### • Pull NATS Docker Image +# Install NATS Message Broker +## Pull NATS docker image -NATS is used for inter-service communication. The only prerequisite here is to install Docker. +NATS is used for inter-service communication. The only pre-requisite here is to install docker. -```bash +``` docker pull nats:latest ``` -### • Run NATS using Docker Compose +## Run NATS using `docker-compose` The `docker-compose.yml` file is available in the root folder. -```bash +``` docker-compose up ``` -## Run CREDEBL Microservices -### • Install Dependencies +## Run CREDEBL Micro-services + ```bash npm install ``` -### • Configure Environment Variables -Configure environment variables in `.env` before you start the API Gateway. +## Configure environment variables in `.env` before you start the API Gateway -### • Running the API Gateway -You can optionally use the `--watch` flag during development/testing. +## Running the API Gateway app +You can optionally use the `--watch` flag during development / testing. ```bash nest start [--watch] ``` -### • Starting Individual Microservices +## Starting the individual Micro-services -For example, to start the `organization service` microservice, run the following command in a separate terminal window: +### e.g. for starting `organization service` micro-service run below command in a separate terminal window ```bash nest start organization [--watch] ``` -Start all the microservices one after another in separate terminal windows: +### Likewise you can start all the micro-services one after another in separate terminal window ```bash nest start user [--watch] @@ -100,9 +89,7 @@ nest start agent-provisioning [--watch] nest start agent-service [--watch] ``` -## Access Microservice Endpoints - -To access microservice endpoints using the API Gateway, navigate to: +## To access micro-service endpoints using the API Gateway. Navigate to ``` http://localhost:5000/api @@ -110,12 +97,12 @@ http://localhost:5000/api ## Credit -The CREDEBL platform is built by AYANWORKS team. +The CREDEBL platform is built by Blockster Labs (Product division of AyanWorks) team. For the core SSI capabilities, it leverages the great work from multiple open-source projects such as Hyperledger Aries, Bifold, Asker, Indy, etc. ## Contributing -Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing — [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. +Pull requests are welcome! Please read our [contributions guide](https://github.com/credebl/platform/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing — [guidance](https://github.com/apps/dco) on this is available. We also welcome issues submitted about problems you encounter in using CREDEBL. ## License diff --git a/agent.env b/agent.env deleted file mode 100644 index 41bf0c008..000000000 --- a/agent.env +++ /dev/null @@ -1,178 +0,0 @@ - -CONNECT_TIMEOUT=10 -MAX_CONNECTIONS=1000 -IDLE_TIMEOUT=30000 -SESSION_ACQUIRE_TIMEOUT=2147483647 -SESSION_LIMIT=2147483647 -INMEMORY_LRU_CACHE_LIMIT=2147483647 -windowMs=1000 -maxRateLimit=800 -BCOVRIN_REGISTER_URL=http://test.bcovrin.vonx.io/register -INDICIO_NYM_URL=https://selfserve.indiciotech.io/nym - -# Contract address for Polygon (mainnet) -SCHEMA_MANAGER_CONTRACT_ADDRESS=0x4B16719E73949a62E9A7306F352ec73F1B143c27 -DID_CONTRACT_ADDRESS=0x0C16958c4246271622201101C83B9F0Fc7180d15 -RPC_URL=https://polygon-rpc.com -# Contract address for Polygon (testnet) -# SCHEMA_MANAGER_CONTRACT_ADDRESS=0x4742d43C2dFCa5a1d4238240Afa8547Daf87Ee7a -# DID_CONTRACT_ADDRESS=0xcB80F37eDD2bE3570c6C9D5B0888614E04E1e49E -# RPC_URL=https://rpc-amoy.polygon.technology - -# Add url and token from your file server -SERVER_URL= -FILE_SERVER_TOKEN= - -BCOVRIN_TEST_GENESIS='{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"138.197.138.255","client_port":9702,"node_ip":"138.197.138.255","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"138.197.138.255","client_port":9704,"node_ip":"138.197.138.255","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"138.197.138.255","client_port":9706,"node_ip":"138.197.138.255","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"138.197.138.255","client_port":9708,"node_ip":"138.197.138.255","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}' - -INDICIO_TEST_GENESIS='{"reqSignature":{},"txn":{"data":{"data":{"alias":"OpsNode","blskey":"4i39oJqm7fVX33gnYEbFdGurMtwYQJgDEYfXdYykpbJMWogByocaXxKbuXdrg3k9LP33Tamq64gUwnm4oA7FkxqJ5h4WfKH6qyVLvmBu5HgeV8Rm1GJ33mKX6LWPbm1XE9TfzpQXJegKyxHQN9ABquyBVAsfC6NSM4J5t1QGraJBfZi","blskey_pop":"Qq3CzhSfugsCJotxSCRAnPjmNDJidDz7Ra8e4xvLTEzQ5w3ppGray9KynbGPH8T7XnUTU1ioZadTbjXaRY26xd4hQ3DxAyR4GqBymBn3UBomLRJHmj7ukcdJf9WE6tu1Fp1EhxmyaMqHv13KkDrDfCthgd2JjAWvSgMGWwAAzXEow5","client_ip":"13.58.197.208","client_port":"9702","node_ip":"3.135.134.42","node_port":"9701","services":["VALIDATOR"]},"dest":"EVwxHoKXUy2rnRzVdVKnJGWFviamxMwLvUso7KMjjQNH"},"metadata":{"from":"Pms5AZzgPWHSj6nNmJDfmo"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"77ad6682f320be9969f70a37d712344afed8e3fba8d43fa5602c81b578d26088"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"cynjanode","blskey":"32DLSweyJRxVMcVKGjUeNkVF1fwyFfRcFqGU9x7qL2ox2STpF6VxZkbxoLkGMPnt3gywRaY6jAjqgC8XMkf3webMJ4SEViPtBKZJjCCFTf4tGXfEsMwinummaPja85GgTALf7DddCNyCojmkXWHpgjrLx3626Z2MiNxVbaMapG2taFX","blskey_pop":"RQRU8GVYSYZeu9dfH6myhzZ2qfxeVpCL3bTzgto1bRbx3QCt3mFFQQBVbgrqui2JpXhcWXxoDzp1WyYbSZwYqYQbRmvK7PPG82VAvVagv1n83Qa3cdyGwCevZdEzxuETiiXBRWSPfb4JibAXPKkLZHyQHWCEHcAEVeXtx7FRS1wjTd","client_ip":"3.17.103.221","client_port":"9702","node_ip":"3.17.215.226","node_port":"9701","services":["VALIDATOR"]},"dest":"iTq944JTtwHnst7rucfsRA4m26x9i6zCKKohETBCiWu"},"metadata":{"from":"QC174PGaL4zA9YHYqofPH2"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"ce7361e44ec10a275899ece1574f6e38f2f3c7530c179fa07a2924e55775759b"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"GlobaliD","blskey":"4Behdr1KJfLTAPNospghtL7iWdCHca6MZDxAtzYNXq35QCUr4aqpLu6p4Sgu9wNbTACB3DbwmVgE2L7hX6UsasuvZautqUpf4nC5viFpH7X6mHyqLreBJTBH52tSwifQhRjuFAySbbfyRK3wb6R2Emxun9GY7MFNuy792LXYg4C6sRJ","blskey_pop":"RKYDRy8oTxKnyAV3HocapavH2jkw3PVe54JcEekxXz813DFbEy87N3i3BNqwHB7MH93qhtTRb7EZMaEiYhm92uaLKyubUMo5Rqjve2jbEdYEYVRmgNJWpxFKCmUBa5JwBWYuGunLMZZUTU3qjbdDXkJ9UNMQxDULCPU5gzLTy1B5kb","client_ip":"13.56.175.126","client_port":"9702","node_ip":"50.18.84.131","node_port":"9701","services":["VALIDATOR"]},"dest":"2ErWxamsNGBfhkFnwYgs4UW4aApct1kHUvu7jbkA1xX4"},"metadata":{"from":"4H8us7B1paLW9teANv8nam"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"0c3b33b77e0419d6883be35d14b389c3936712c38a469ac5320a3cae68be1293"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"IdRamp","blskey":"LoYzqUMPDZEfRshwGSzkgATxcM5FAS1LYx896zHnMfXP7duDsCQ6CBG2akBkZzgH3tBMvnjhs2z7PFc2gFeaKUF9fKDHhtbVqPofxH3ebcRfA959qU9mgvmkUwMUgwd21puRU6BebUwBiYxMxcE5ChReBnAkdAv19gVorm3prBMk94","blskey_pop":"R1DjpsG7UxgwstuF7WDUL17a9Qq64vCozwJZ88bTrSDPwC1cdRn3WmhqJw5LpEhFQJosDSVVT6tS8dAZrrssRv2YsELbfGEJ7ZGjhNjZHwhqg4qeustZ7PZZE3Vr1ALSHY4Aa6KpNzGodxu1XymYZWXAFokPAs3Kho8mKcJwLCHn3h","client_ip":"207.126.128.12","client_port":"9702","node_ip":"207.126.129.12","node_port":"9701","services":["VALIDATOR"]},"dest":"5Zj5Aec6Kt9ki1runrXu87wZ522mnm3zwmaoHLUcHLx9"},"metadata":{"from":"AFLDFPoJuDQUHqnfmg8U7i"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"c9df105558333ac8016610d9da5aad1e9a5dd50b9d9cc5684e94f439fa10f836"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"idlab-node01","blskey":"2fjJVi33U1tCTjW77cJaf1NLz7EzWkVNzR9BEQpVVK64MJpRKNUzt6k7Td2U8yqU5hGyAFH5N7ZymSB55TnpC3rJYLVTcGXZeXpmrQx3mwnXNyfTDnxfTpdQ1KMoFeZoDPZ8acfaH8GWeW2jL1qREE52tetBf4tXTeshmWzGkEN7r4y","blskey_pop":"RSjiM6dYUmN2rv2ca7dUCmEKrivq12rhxhXUKHdmSwUxbCmcijsgoERjYG7MqxhKLjSAJ5715K23fVEc6uK1kTenKmYCcCts8MLMAQG8Upb22nfgHJ3py8RwRoACeAjFF3myAMNRJJPhUdv96drJdwkGRv7f6JjvoB5KWVQYTNgheP","client_ip":"205.159.92.17","client_port":"9702","node_ip":"205.159.92.16","node_port":"9701","services":["VALIDATOR"]},"dest":"8czYgwmLDazVrBHuo53Tyx7Tw8ZhvnoC2BfhQGir4r8F"},"metadata":{"from":"PN8wFxLKjdkwyxoEEXwyz2"},"type":"0"},"txnMetadata":{"seqNo":5,"txnId":"9237eca7d2a203f6e1779f63064d2f22cf28e1bcd4e6fe5d791b15e82969acdc"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"lorica-identity-node1","blskey":"wUh24sVCQ8PHDgSb343g2eLxjD5vwxsrETfuV2sbwMNnYon9nhbaK5jcWTekvXtyiwxHxuiCCoZwKS97MQEAeC2oLbbMeKjYm212QwSnm7aKLEqTStXht35VqZvZLT7Q3mPQRYLjMGixdn4ocNHrBTMwPUQYycEqwaHWgE1ncDueXY","blskey_pop":"R2sMwF7UW6AaD4ALa1uB1YVPuP6JsdJ7LsUoViM9oySFqFt34C1x1tdHDysS9wwruzaaEFui6xNPqJ8eu3UBqcFKkoWhdsMqCALwe63ytxPwvtLtCffJLhHAcgrPC7DorXYdqhdG2cevdqc5oqFEAaKoFDBf12p5SsbbM4PYWCmVCb","client_ip":"35.225.220.151","client_port":"9702","node_ip":"35.224.26.110","node_port":"9701","services":["VALIDATOR"]},"dest":"k74ZsZuUaJEcB8RRxMwkCwdE5g1r9yzA3nx41qvYqYf"},"metadata":{"from":"Ex6hzsJFYzNJ7kzbfncNeU"},"type":"0"},"txnMetadata":{"seqNo":6,"txnId":"6880673ce4ae4a2352f103d2a6ae20469dd070f2027283a1da5e62a64a59d688"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"cysecure-itn","blskey":"GdCvMLkkBYevRFi93b6qaj9G2u1W6Vnbg8QhRD1chhrWR8vRE8x9x7KXVeUBPFf6yW5qq2JCfA2frc8SGni2RwjtTagezfwAwnorLhVJqS5ZxTi4pgcw6smebnt4zWVhTkh6ugDHEypHwNQBcw5WhBZcEJKgNbyVLnHok9ob6cfr3u","blskey_pop":"RbH9mY7M5p3UB3oj4sT1skYwMkxjoUnja8eTYfcm83VcNbxC9zR9pCiRhk4q1dJT3wkDBPGNKnk2p83vaJYLcgMuJtzoWoJAWAxjb3Mcq8Agf6cgQpBuzBq2uCzFPuQCAhDS4Kv9iwA6FsRnfvoeFTs1hhgSJVxQzDWMVTVAD9uCqu","client_ip":"35.169.19.171","client_port":"9702","node_ip":"54.225.56.21","node_port":"9701","services":["VALIDATOR"]},"dest":"4ETBDmHzx8iDQB6Xygmo9nNXtMgq9f6hxGArNhQ6Hh3u"},"metadata":{"from":"uSXXXEdBicPHMMhr3ddNF"},"type":"0"},"txnMetadata":{"seqNo":7,"txnId":"3c21718b07806b2f193b35953dda5b68b288efd551dce4467ce890703d5ba549"},"ver":"1"}' - -CREDEBL_TEST_GENESIS='{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node1", "blskey": "4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba", "blskey_pop": "RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1", "client_ip": "192.168.1.74", "client_port": 9702, "node_ip": "192.168.1.74", "node_port": 9701, "services": ["VALIDATOR"]}, "dest": "Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"}, "metadata": {"from": "Th7MpTaRZVRYnPiabds81Y"}, "type": "0"}, "txnMetadata": {"seqNo": 1, "txnId": "fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"}, "ver": "1"} -{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node2", "blskey": "37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk", "blskey_pop": "Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5", "client_ip": "192.168.1.74", "client_port": 9704, "node_ip": "192.168.1.74", "node_port": 9703, "services": ["VALIDATOR"]}, "dest": "8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"}, "metadata": {"from": "EbP4aYNeTHL6q385GuVpRV"}, "type": "0"}, "txnMetadata": {"seqNo": 2, "txnId": "1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"}, "ver": "1"} -{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node3", "blskey": "3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5", "blskey_pop": "QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh", "client_ip": "192.168.1.74", "client_port": 9706, "node_ip": "192.168.1.74", "node_port": 9705, "services": ["VALIDATOR"]}, "dest": "DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"}, "metadata": {"from": "4cU41vWW82ArfxJxHkzXPG"}, "type": "0"}, "txnMetadata": {"seqNo": 3, "txnId": "7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"}, "ver": "1"} -{"reqSignature": {}, "txn": {"data": {"data": {"alias": "Node4", "blskey": "2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw", "blskey_pop": "RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP", "client_ip": "192.168.1.74", "client_port": 9708, "node_ip": "192.168.1.74", "node_port": 9707, "services": ["VALIDATOR"]}, "dest": "4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"}, "metadata": {"from": "TWwCRQRZ2ZHMJFn9TzLp7W"}, "type": "0"}, "txnMetadata": {"seqNo": 4, "txnId": "aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"}, "ver": "1"}' - -SOVRIN_STAGING_NET='{"reqSignature":{},"txn":{"data":{"data":{"alias":"australia","client_ip":"52.64.96.160","client_port":"9702","node_ip":"52.64.96.160","node_port":"9701","services":["VALIDATOR"]},"dest":"UZH61eLH3JokEwjMWQoCMwB3PMD6zRBvG6NCv5yVwXz"},"metadata":{"from":"3U8HUen8WcgpbnEz1etnai"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"c585f1decb986f7ff19b8d03deba346ab8a0494cc1e4d69ad9b8acb0dfbeab6f"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"brazil","client_ip":"54.233.203.241","client_port":"9702","node_ip":"54.233.203.241","node_port":"9701","services":["VALIDATOR"]},"dest":"2MHGDD2XpRJohQzsXu4FAANcmdypfNdpcqRbqnhkQsCq"},"metadata":{"from":"G3knUCmDrWd1FJrRryuKTw"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"5c8f52ca28966103ff0aad98160bc8e978c9ca0285a2043a521481d11ed17506"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"canada","client_ip":"52.60.207.225","client_port":"9702","node_ip":"52.60.207.225","node_port":"9701","services":["VALIDATOR"]},"dest":"8NZ6tbcPN2NVvf2fVhZWqU11XModNudhbe15JSctCXab"},"metadata":{"from":"22QmMyTEAbaF4VfL7LameE"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"408c7c5887a0f3905767754f424989b0089c14ac502d7f851d11b31ea2d1baa6"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"england","client_ip":"52.56.191.9","client_port":"9702","node_ip":"52.56.191.9","node_port":"9701","services":["VALIDATOR"]},"dest":"DNuLANU7f1QvW1esN3Sv9Eap9j14QuLiPeYzf28Nub4W"},"metadata":{"from":"NYh3bcUeSsJJcxBE6TTmEr"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"d56d0ff69b62792a00a361fbf6e02e2a634a7a8da1c3e49d59e71e0f19c27875"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"korea","client_ip":"52.79.115.223","client_port":"9702","node_ip":"52.79.115.223","node_port":"9701","services":["VALIDATOR"]},"dest":"HCNuqUoXuK9GXGd2EULPaiMso2pJnxR6fCZpmRYbc7vM"},"metadata":{"from":"U38UHML5A1BQ1mYh7tYXeu"},"type":"0"},"txnMetadata":{"seqNo":5,"txnId":"76201e78aca720dbaf516d86d9342ad5b5d46f5badecf828eb9edfee8ab48a50"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"singapore","client_ip":"13.228.62.7","client_port":"9702","node_ip":"13.228.62.7","node_port":"9701","services":["VALIDATOR"]},"dest":"Dh99uW8jSNRBiRQ4JEMpGmJYvzmF35E6ibnmAAf7tbk8"},"metadata":{"from":"HfXThVwhJB4o1Q1Fjr4yrC"},"type":"0"},"txnMetadata":{"seqNo":6,"txnId":"51e2a46721d104d9148d85b617833e7745fdbd6795cb0b502a5b6ea31d33378e"},"ver":"1"} -{"reqSignature":{},"txn":{"data":{"data":{"alias":"virginia","client_ip":"34.225.215.131","client_port":"9702","node_ip":"34.225.215.131","node_port":"9701","services":["VALIDATOR"]},"dest":"EoGRm7eRADtHJRThMCrBXMUM2FpPRML19tNxDAG8YTP8"},"metadata":{"from":"SPdfHq6rGcySFVjDX4iyCo"},"type":"0"},"txnMetadata":{"seqNo":7,"txnId":"0a4992ea442b53e3dca861deac09a8d4987004a8483079b12861080ea4aa1b52"},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"3U8HUen8WcgpbnEz1etnai","value":"NXjsBfaDijk6P6W6fg1EKrzPYhDvkNHbDcSMaYdja4URdiEdaPFcqXQttgwytcKZL79BsV3i8ShWbDS5L9Um5Pj"}]},"txn":{"data":{"data":{"alias":"australia","blskey":"31My1Ya9D1v5edgkGfYb96k4HWN1GwWWUeEnzzgw3NpiVmjpyjKgPmTYvPWZAYt8CLJLWzoQrEcBYhKRedsx8JMEB4LyPVx5vgbcjKsiUK2985t9Pkpn45UAYjDvVmGSbF2y99mMjQxpt7nCwGZ9yKcEm1cLpyHxvbnceZGkf8e9HYs"},"dest":"UZH61eLH3JokEwjMWQoCMwB3PMD6zRBvG6NCv5yVwXz"},"metadata":{"digest":"f8297516300f34624d25bf38b558f8ac9df2830a4e7fe8ccdf6816ec597da4cc","from":"3U8HUen8WcgpbnEz1etnai","reqId":1518718611795589},"type":"0"},"txnMetadata":{"seqNo":8,"txnTime":1518718611},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"3U8HUen8WcgpbnEz1etnai","value":"4wiCViKevghYdtcdJXdAmS4Cwy9dAsc5eDUqHnjZXcnBbVpc71iHdyWj83U4teK65Yq2g2no8ddzJscEVTZn1ueC"}]},"txn":{"data":{"data":{"alias":"australia","blskey":"KMbkBaLigL6wUbYZmh3d41EeCRVrW1hWkpGRcy2CXZ6ugZF1Zb7ZeL3RPRasaARYkuWmjYAuLE8WiVC1dL5ZQuJczAJfDsk73hxivHxqeBaXDQwEBN2dESLZHdK3oMU79ZhqBqnEgvvAvmVyyneW661if5c45AFJgGYPtpenxS49MB"},"dest":"UZH61eLH3JokEwjMWQoCMwB3PMD6zRBvG6NCv5yVwXz"},"metadata":{"digest":"77ab856e84ef72f87a6e99ecd447da338d1eddf332b9cd33a1399aba98d39ef4","from":"3U8HUen8WcgpbnEz1etnai","reqId":1518718754041702},"type":"0"},"txnMetadata":{"seqNo":9,"txnTime":1518718754},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"G3knUCmDrWd1FJrRryuKTw","value":"5owgEB8jgYUaGsH3pV6DyFMSiwXPqMgM4u9NzgCyJpnENTzyF1BmeVaNomRunbL9R4EhfbrDNHW9RJFd5GT5pAPs"}]},"txn":{"data":{"data":{"alias":"brazil","blskey":"2G1tp8pjdRSiZnpsWpN5c4tnGGTCPbqEkf8MyaVnfSxBun7pdtRqq83E7XnY4uzNmzpBF5PZcPBonfZXzCT2qWjRAB7PaDUWU5zWfLKhNoRmEzaeFp2dVkd9XrcefSfynStWsiPmv4tG8CHX153kL9Le7LMBk9qCRjeXn77wCUXqyvB"},"dest":"2MHGDD2XpRJohQzsXu4FAANcmdypfNdpcqRbqnhkQsCq"},"metadata":{"digest":"999bb3eecd2807ebf31c619518775f6219a1a6e51234f3a1f3b1021dd900dd97","from":"G3knUCmDrWd1FJrRryuKTw","reqId":1518719363728031},"type":"0"},"txnMetadata":{"seqNo":10,"txnTime":1518719363},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"22QmMyTEAbaF4VfL7LameE","value":"5yVCBN5z8ehtM14FdGcRPDEc3asUrEH83PdbEBpfyrvHprn8iUZMBSCo4kkirvS1HirLLxU4mHssGvm4baGfTXfc"}]},"txn":{"data":{"data":{"alias":"canada","blskey":"q6nBf5jDDQN23yKEvVsxYjMkS843yspF44867S9Fhht2uUogbAed4cXLfxdTJMLYvsNT2fhA2jmnNSQwUyWfXjBFUSZqNhvJdC3d5XvcW8aqCGnV1BY9fR1kvrQXoLjaYgr1Je33NJxLNpJLumGF6WvuW6SuZYJPbfWxo7F1Vhy8oV"},"dest":"8NZ6tbcPN2NVvf2fVhZWqU11XModNudhbe15JSctCXab"},"metadata":{"digest":"306bb20ca4c46e485e0c93abd6ee9960d8954928d5603d79ade75c7ef22c4288","from":"22QmMyTEAbaF4VfL7LameE","reqId":1518719479176817},"type":"0"},"txnMetadata":{"seqNo":11,"txnTime":1518719479},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"NYh3bcUeSsJJcxBE6TTmEr","value":"G99xrEafV1BhUtgLb8jugDQvu4JLp5R2s5T4ZqSuy5pPNnDRZyhWn6iW89Uwe476uxxdugx7TzEXY61wZXCsUNs"}]},"txn":{"data":{"data":{"alias":"england","blskey":"3TXrLKV5Yn2BE47NBEvM9u6J2DUsn414sUhQQQN1X2mRKhsPvWnixqo1AbFC5kRVjpHDhRPzvenm7cApfGcCGMDME1mSwESxiYgkgpahc9DuGD5hvFieryk3yJ96jcumWA7NUUDYmiHhZfCThXvGS9agXK4Kt3sgxBYQ17yN7wj3cRN"},"dest":"DNuLANU7f1QvW1esN3Sv9Eap9j14QuLiPeYzf28Nub4W"},"metadata":{"digest":"fb5062d61960bd6f742519c6df9ca5463e6c83689cad58a7c84783a7d013c3fb","from":"NYh3bcUeSsJJcxBE6TTmEr","reqId":1518719555172082},"type":"0"},"txnMetadata":{"seqNo":12,"txnTime":1518719555},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"U38UHML5A1BQ1mYh7tYXeu","value":"4ns8r24bUMZqs2AVrBUpK66gGBA3pp2h98BM8XC2nHghbyrmWA6KpSjnmEfLKBgDZcFaoSX4Wu2d6TJzeRsVQQ8Q"}]},"txn":{"data":{"data":{"alias":"korea","blskey":"2b51xiHs4afNBiTUenKJ2XHmPMfYcNFHAwB2x39z953y1YawDTKnUW9Q2gPCQvRR5esvF235PHfv9b5GYFnXPo41wzotm7LiYsYimAarVh2PFo3CAz5DSo9xA6Xo9EhP2JnDSvi2APqGn2UpoYtpRtz2bMFurqrnw6UPz4vq91x23hJ"},"dest":"HCNuqUoXuK9GXGd2EULPaiMso2pJnxR6fCZpmRYbc7vM"},"metadata":{"digest":"4943fc79d796067841ad0aeb0e44c54bb88a1e152e2bc482925ebe3df9e5d032","from":"U38UHML5A1BQ1mYh7tYXeu","reqId":1518719649363976},"type":"0"},"txnMetadata":{"seqNo":13,"txnTime":1518719649},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"HfXThVwhJB4o1Q1Fjr4yrC","value":"2pC8hkN3MxyJeUZmkqhvvUEi3uCwTbKd14Yjc4uVJjNqx2Hj6oXvwaZPvmJn3VJMkKKE7tpFrtwyFXx8CJ5WwArf"}]},"txn":{"data":{"data":{"alias":"singapore","blskey":"CT7HsX8MAcAnWZ8CFF1ttdYG91hNc7K9dGfpcp4QprLRYVR2XSr2ywHuNT5zLPvTkGDjrjyF2HdMbLkdNGgRa5LH1Am3D619yycJjP8t51c2XygEjoa6J1TmUjYkuC44Q6Aq1BriX5hJ2oxJL3bvnM2g7QRzRPyFdM771zNutV72W3"},"dest":"Dh99uW8jSNRBiRQ4JEMpGmJYvzmF35E6ibnmAAf7tbk8"},"metadata":{"digest":"2dce61e98838e65cd7c303adc248c8fabeab4ecc0492f16572521b2f1939c975","from":"HfXThVwhJB4o1Q1Fjr4yrC","reqId":1518719769599617},"type":"0"},"txnMetadata":{"seqNo":14,"txnTime":1518719769},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"SPdfHq6rGcySFVjDX4iyCo","value":"2xLQwrn2jvg6XZcXGuW6AQ3PKiSHPNAdaHM6CVb8iwDA15HHoPUSi2PGkFvUyFya82QiCA22Y11NDX4Yh4Kx7DqR"}]},"txn":{"data":{"data":{"alias":"virginia","blskey":"372y1y4t9JdTtkyA4C5ANi88YGGaBtSpWd1FL6TJawxn1gnkebpztpsiN5AjTkwARMsTZWX8VyBZ3UGhPd7grmVgoBogTBf1LyvpnmVJR2p9TC26fDFz9GFhynAcPfHQ3xLvVjSyAYH8JjEHRS2yMXxhq8gZCTy494shNXP1wKCD8Ny"},"dest":"EoGRm7eRADtHJRThMCrBXMUM2FpPRML19tNxDAG8YTP8"},"metadata":{"digest":"113c1a81394cbadc48d57aef9d9eb93722f318d01261ed3dc162d8e0f527e37a","from":"SPdfHq6rGcySFVjDX4iyCo","reqId":1518719862981841},"type":"0"},"txnMetadata":{"seqNo":15,"txnTime":1518719863},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"A3h7JbvErKPCfaJx3VNRJ9","value":"4jzEdvJYAKB4vMNMU2VVe4dvGwT2fy28WPuvZipVEqf6R7qkuDWBXLYLBSzGAkUErtRKUnb7KH2eSrZSEgHRozTx"}]},"txn":{"data":{"data":{"alias":"ibm","blskey":"Wv1XfnPWngRPQNQnnaMsewidNtc6Rc2mx842q5ApKTgUdUs2YBZZFgSw4TdWa8HVMZvptiukdooXhTimEwxw9dFQRK5faYb2LBdeRZ6RR2kXz39a6vjyG71arL7Q6FnetH6N2NMFBACdu1PBUYKWAyBn5K8ZUUGgt2YhmZJ9DnieLa","client_ip":"169.60.5.114","client_port":9702,"node_ip":"169.60.5.114","node_port":9701,"services":["VALIDATOR"]},"dest":"Eq7m7GMFKPeq5Ek3HH1PkHxzZ46R9VL1Eube3U9wfjp5"},"metadata":{"digest":"a1dfd555da4085210b53245a199a7d431f4a6328e8936050ec3dc794fe5a2b07","from":"A3h7JbvErKPCfaJx3VNRJ9","reqId":1518798036389445},"type":"0"},"txnMetadata":{"seqNo":16,"txnTime":1518798036},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TnxHS11bsfWrzzi612R2X8","value":"5tHfCPQfV2XAqo7V42akRqmtDqmbX4HUMAZSy9xKz6vERMSDFHmraW4ZEK9hUacjiRthn7P5KuLmopu1mCLwWXhq"}]},"txn":{"data":{"data":{"alias":"RFCU","blskey":"3FuY8wqmBi2XxL4EtJDo3Lhad48QN5ZbhH4kEV3Kfkeb53x94qmWfRndZQJo91M3aWtYpZaZqDaqrQMpuEcAvh8g9hLmb9BWbhK6BVvACSJ2RUiDhBRp4NPkg93tNmQjkdzWoUznFZvbPTTBPBcsmngTake7Sm4YLf1tbd8vuF7Nqkd","client_ip":"207.108.62.234","client_port":9702,"node_ip":"207.108.62.234","node_port":9701,"services":["VALIDATOR"]},"dest":"2B8bkZX3SvcBq3amP7aeATsSPz82RyyCJQbEjZpLgZLh"},"metadata":{"digest":"54d0fb8b9ebc2167d16f9d2027b49917fb484ec6e33caad48bf1516ba27460af","from":"TnxHS11bsfWrzzi612R2X8","reqId":1518798718080714},"type":"0"},"txnMetadata":{"seqNo":17,"txnTime":1518798718},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"ApndPYajjcdTnpvopThGwh","value":"5eyGhLmKmJx5SzkqmS5LD4s4rkQHDAeGh7PmZKhTNJ8MxdbSaZEGjVj4XYD37GeGTepfLwmivJDJjfYaDQGV7aoi"}]},"txn":{"data":{"data":{"alias":"pcValidator01","blskey":"3NU2sWYG7eeJY66W1FGpLgLaDS9fDQfKMNgPGMCk9iTcatMd4XdmAF5UqULkLUpGWABftNTrRsgm82DpfJ1cTu7CTB84KYoW4SYf7Rq4a2wi6rVbmU6k76ZYgmny6h8vqBbdRwozxVTjkPyzV6Z5MSA3vrDdf31iiiPEcXTTT3oFhKC","client_ip":"52.175.254.49","client_port":9799,"node_ip":"52.175.254.49","node_port":9701,"services":["VALIDATOR"]},"dest":"5fKwygs8KEGoUPGa65qz1oCm7h6Fb7HrML9r4jmZ9cic"},"metadata":{"digest":"9dd9cd0dc1ee21694c798c41e80922afd44e70b4e067b2c9c3582c929248ee74","from":"ApndPYajjcdTnpvopThGwh","reqId":1518809271040696},"type":"0"},"txnMetadata":{"seqNo":18,"txnTime":1518809270},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"G4zXq8L7L8n73WKSVv2HxY","value":"Yyd84xQCFWTQovZjUFohaXp3UhXdHX4wj25CGnpvcQs9LKE37DRj4DWvh2RkJALeBLtPZJsXwzKyuivPxLA8QVj"}]},"txn":{"data":{"data":{"alias":"NewtonD","blskey":"36fw9Bz4tLCkzZhUQdA2N51AZWCF6sdyiDuAX7WuNsqeq3C2dCzvS9GeSxt9t1BWxtHWc2GJDVBcqZKGh7Tg2eoQa1KTPhPFRdShBhYPDfP85gKKshHa5aEdLBvTsUkADaDoyFv4rhqTLDFbdcu4WsQE59pxyj3QTSAURgRhxkcm2oJ","client_ip":"52.165.40.82","client_port":9701,"node_ip":"40.69.165.222","node_port":9777,"services":["VALIDATOR"]},"dest":"HU8AkmtsqvcfEtvdWAZgZZFfvKYH8vu2YdgkkHrmNDGP"},"metadata":{"digest":"e0afed7d71f511362c2b21cb90c0b68fc1cb176e233d147a267375350133a6b6","from":"G4zXq8L7L8n73WKSVv2HxY","reqId":1519949508036643},"type":"0"},"txnMetadata":{"seqNo":19,"txnTime":1519949508},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"EKvw1VdXwS2pWKLLyBLCDs","value":"DqCfNstgx5q8gaM57b6qUooHCuhFFqqs7H995UDSBARoD7LWn6gaqJAmcz56NMUmtiWSqpqreBFxwPMr66xPj2Z"}]},"txn":{"data":{"data":{"alias":"Aalto","blskey":"emFqQUM4yqEWdhbk8KzLry6okMk5MsQUoR922BdBS8KBFxNAofPPDzDSR6pwu8ytZVVrfWGbVBs3D6WDt8dZAQY1xYJoAax7pt9Bkgen5Tc2BM5dLhuHpDwRLAmXFFxQbTxArAKidQeeB9wBJbScbbVnWjNNiYPreaZjgFXUGoWjSS","client_ip":"130.233.224.231","client_port":9702,"node_ip":"130.233.224.231","node_port":9701,"services":["VALIDATOR"]},"dest":"7JYQmTE6mBxa5RAZwXAj4bxqetAy64tcEUShqzJjLRrE"},"metadata":{"digest":"5c717908d4b75e92767cca818ac7530789c765c08207eec38cd18eff93b76502","from":"EKvw1VdXwS2pWKLLyBLCDs","reqId":1520418048198552},"type":"0"},"txnMetadata":{"seqNo":20,"txnTime":1520418048},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"Qm7Ugni6jFRpfbPkzhXzXH","value":"jAGG7yFmyLXRGUS2yqoNikvCGWhWeS9eFRyb5vfzu9ZjuAqbiyL4SfNv8NpMAs9Bsda5Y9kZfg9wrW8GufX8NNN"}]},"txn":{"data":{"data":{"alias":"Stuard","blskey":"4Yry2Z17vf1Hf24HvRrduj3Zi5LBJ1x7PbDqNWX19RxHcYJVjpy2f9qriZk7Fx812Xip6LEhsEXWRB1qKujBwdLNbCvMFYnJK2kS2B9HNgDgbVwDbpw16QNuJMtUnvQv1B6vCmci96gypYWMvZmJ6p9qsPXA7CZ2ZSjRuLg3RqvD7y4","client_ip":"10.0.0.10","client_port":9702,"node_ip":"10.0.0.10","node_port":9701,"services":["VALIDATOR"]},"dest":"C4e4rEwPZ4bM341VEL9ysnAgBiMW42RH3UmbriPuzoCb"},"metadata":{"digest":"70d1ed111dd5fec2e46378152f0e8ed5a99728e06288f721fad337b8ba9bae3c","from":"Qm7Ugni6jFRpfbPkzhXzXH","reqId":1521022519350930},"type":"0"},"txnMetadata":{"seqNo":21,"txnTime":1521022519},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"Qm7Ugni6jFRpfbPkzhXzXH","value":"5yKYfGouHn494CtFhEMLYYuWHC9rPhsUWBpXLneJT8jt869rjVxeab9iABYS2Uq55Avjwtd25Xfvyru57HwQMfZb"}]},"txn":{"data":{"data":{"alias":"Stuard","blskey":"4Yry2Z17vf1Hf24HvRrduj3Zi5LBJ1x7PbDqNWX19RxHcYJVjpy2f9qriZk7Fx812Xip6LEhsEXWRB1qKujBwdLNbCvMFYnJK2kS2B9HNgDgbVwDbpw16QNuJMtUnvQv1B6vCmci96gypYWMvZmJ6p9qsPXA7CZ2ZSjRuLg3RqvD7y4","client_ip":"185.27.183.66","client_port":9702,"node_ip":"185.27.183.66","node_port":9701,"services":["VALIDATOR"]},"dest":"C4e4rEwPZ4bM341VEL9ysnAgBiMW42RH3UmbriPuzoCb"},"metadata":{"digest":"0c7f5c756b286dcf1c769cec7a3d5cfeaf57c38ac25e4a83e9d37b417b20774c","from":"Qm7Ugni6jFRpfbPkzhXzXH","reqId":1521070797707610},"type":"0"},"txnMetadata":{"seqNo":22,"txnTime":1521070797},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"FzUUYiVKCDnSWd77NHfhpZ","value":"3Q3ijuEPihRyGsZFvmb414AWWQ7iMskCHoP7bC14FgPesNdPNY3pdfddEfPf5FrCg4wzdvdAFjKaBunHHwXCG4V3"}]},"txn":{"data":{"data":{"alias":"TNO","blskey":"37d7DmcwGWM7yfnpwLGzwVy6zZwoc6cAgeeSJFBWbVh6jq5tP8dPf7s2XDxxtWafmr1JdyzycBcNztEsE8Uf9qX2jRoXzhCnjEEYJCAByEn5hWC2VQ9EqkuKzq28Vob7Piof7rEJeUPxuBZtrXL1khyTN2waQtix6CYtv9QejNPZVJ2","client_ip":"134.221.127.143","client_port":9702,"node_ip":"134.221.127.143","node_port":9701,"services":["VALIDATOR"]},"dest":"TZxmZoXwNk1X5o48pXqbDFz6mTJT5QkiRme9z5p86KQ"},"metadata":{"digest":"0f3b44855c6f562b17321efe51f00edab1cdbe8608c667669e8085430fa32cc5","from":"FzUUYiVKCDnSWd77NHfhpZ","reqId":1521195244613677},"type":"0"},"txnMetadata":{"seqNo":23,"txnTime":1521195244},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"3jN1g2dAPKx5giEKnc5k9GiYHBw5yFZD8D8GiuFBL7wuHUDabobCeCCDezboxqkCpHSLsVE5hDzq6RQWUKwHXwv1"}]},"txn":{"data":{"data":{"alias":"Stuard","services":[]},"dest":"C4e4rEwPZ4bM341VEL9ysnAgBiMW42RH3UmbriPuzoCb"},"metadata":{"digest":"f24b5dda0fe64addafbbdd67aa2be5731064d6c6215d56e883e67009df0ce0d8","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1521760233710703},"type":"0"},"txnMetadata":{"seqNo":24,"txnTime":1521761250},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5omTkQAe1LmYQfY4PdYf3YEjVM1jBs2Qr8x8LAhghD3ymeudEvm1dyk3nQtEtNzFvAeGhnajWMA862DWM4Hg51vv"}]},"txn":{"data":{"data":{"alias":"Aalto","services":[]},"dest":"7JYQmTE6mBxa5RAZwXAj4bxqetAy64tcEUShqzJjLRrE"},"metadata":{"digest":"f526af4c7569c941f69f1fb2dc3ad6ef98bdef2aa2aceca33d43dc92900d65bb","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1521822633807532},"type":"0"},"txnMetadata":{"seqNo":25,"txnTime":1521822633},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"4YtbkvnTMfMA6d9FjkjBcueFGq5PnLoHFKn8HAGiLKxcob2QM6KsdwLRapQcbtn9N33NimyeFzrHCerzAcbS8fVq"}]},"txn":{"data":{"data":{"alias":"pcValidator01","services":[]},"dest":"5fKwygs8KEGoUPGa65qz1oCm7h6Fb7HrML9r4jmZ9cic"},"metadata":{"digest":"f0986bab87b08031cfaeb58a06e27e8c1214f4c20af0e26c709c1c3970c0b241","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1521825598199425},"type":"0"},"txnMetadata":{"seqNo":26,"txnTime":1521825598},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"G4zXq8L7L8n73WKSVv2HxY","value":"5kkkxWBUmYYcrFSKQcgaAEhcsiwJft74Js5VpwBzL1ssfFJRJgDdjhW27hZRCao994DW5b17Xgw6UqSyBLP7UmyD"}]},"txn":{"data":{"data":{"alias":"NewtonD","services":[]},"dest":"HU8AkmtsqvcfEtvdWAZgZZFfvKYH8vu2YdgkkHrmNDGP"},"metadata":{"digest":"e2d73bb8154e3372fa0c8d57c1383c2b4710308198a76580f15ce2c3ffd5f204","from":"G4zXq8L7L8n73WKSVv2HxY","reqId":1521831913747645},"type":"0"},"txnMetadata":{"seqNo":27,"txnTime":1521831913},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"G4zXq8L7L8n73WKSVv2HxY","value":"4L9AFEy3gdV1KNHsuG4gWSuETzTmJ2aD5TzSdWiQCm13o6GfNAY8YoemyiTiwnX8mzxdghgc38CLhEBm3DwfC3Dv"}]},"txn":{"data":{"data":{"alias":"NewtonD","services":["VALIDATOR"]},"dest":"HU8AkmtsqvcfEtvdWAZgZZFfvKYH8vu2YdgkkHrmNDGP"},"metadata":{"digest":"d83f3622a59d8fe15448389a185752add786de3eaf0cabfc17cea86dc503c2a5","from":"G4zXq8L7L8n73WKSVv2HxY","reqId":1521833231516195},"type":"0"},"txnMetadata":{"seqNo":28,"txnTime":1521833231},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"xzjjfKfJJJeQxZb5WhMYyHDcDG5dZCAvsYmBh6FtP9J1ckQUazaJ7AC2ksHzjARW9kkcFBS4B1M4R7y5Bc3BN5S"}]},"txn":{"data":{"data":{"alias":"TNO","services":[]},"dest":"TZxmZoXwNk1X5o48pXqbDFz6mTJT5QkiRme9z5p86KQ"},"metadata":{"digest":"03c5763b3be5b2c53be3f2af041071bdb2f2aa08754adbdcebbcbbe055036cd6","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1522073964061797},"type":"0"},"txnMetadata":{"seqNo":29,"txnTime":1522073964},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"nm4hZxHEb86rDjHddyuFgnuy7hS7bf1Biifnje3c75Kgn1eQ5frc9xwVWFUzZNFntfVckXQK3U97wRiiP7MrijK"}]},"txn":{"data":{"data":{"alias":"NewtonD","services":[]},"dest":"HU8AkmtsqvcfEtvdWAZgZZFfvKYH8vu2YdgkkHrmNDGP"},"metadata":{"digest":"a81744dd61919ea58d75904a8d6921be18362cecb174e3d5f318384c99d912c2","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1523554207793968},"type":"0"},"txnMetadata":{"seqNo":30,"txnTime":1523554207},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"LKjUWXR3QpGsMkgB2XFyzt","value":"671v9pVTeRwN4XybdY5tjGyJZCNhGPFq3bcBxymCGJ18EQbBdDa6LXGh3brvWQ53yncspYbywrgC7eGknnqYR2GB"}]},"txn":{"data":{"data":{"alias":"VeridiumIDC","blskey":"2HhwAzNXb6qrptphzJKiAYqGtE6dNNcK8Q33EJU8hNnAhvjC4X1Bk65MbgvPMpn4rP9HZAH78StG12HfU6VyLd6JBbp6gkgwvtXUK1QefTEGcRipj3XnVJ7tjU8KzxWqaZQW5exJQQQhmedCKZphKvvnb2wuVcoBVS3Ad3ZLm2d4apo","client_ip":"18.197.183.58","client_port":9702,"node_ip":"18.197.183.58","node_port":9701,"services":["VALIDATOR"]},"dest":"58uCeMaEiMHSi8MdEdcgVbpmzMKmiLSYCEz1vxPGJND6"},"metadata":{"digest":"64a71eb01fc9becdbea811aa449d9a588c22b47e7d7434b4dae437bf8153303f","from":"LKjUWXR3QpGsMkgB2XFyzt","reqId":1524737343827561},"type":"0"},"txnMetadata":{"seqNo":31,"txnTime":1524737343},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"RtMtkBrkHCEDtGXXqRfGoV","value":"3ontWhK6hYCpGx8DgirvihSqDarZUTQ3NacwtC4okP2BNpF1xp6tzjnx6iigt8kgaRSGPacXL93AHVXeS6fvQ41t"}]},"txn":{"data":{"data":{"alias":"oregon","blskey":"2bQaqiLhyAEJh6t1xhC3jKk5PRc7v8AsEH9dxSjgVvBqwrmoiDKfSmJKBXA9kCT1uQkpaGeYyP4bkb3RA1ABYebqcswkjdVUGRZYqyxasdBD2phUbNm6WNaGtyRkhJ265KZ7YP8QaDS3NCwTXT7b8vMNmaTL8Kd1Buvaq7gS3ZLJXU6","client_ip":"18.236.138.206","client_port":9702,"node_ip":"18.236.138.206","node_port":9701,"services":["VALIDATOR"]},"dest":"4wdqCSyPeiqCbQhjsbP9Xjasm7vuU7ithawkpuoBXCBM"},"metadata":{"digest":"add1f55e950e2411deecd700285802fd154fc68668e2020df9e97cad47d79491","from":"RtMtkBrkHCEDtGXXqRfGoV","reqId":1524852414943816},"type":"0"},"txnMetadata":{"seqNo":32,"txnTime":1524852415},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"2XCnRcUZJH2JEzotFHTqin","value":"5g4zsXjPumLMTVb8rCYoALeERtgue7qq6uda1vxA2wywZc5VDLBmrJbguwMztdXLCJQKg3qbu8bcYdJUERNdX1Bq"}]},"txn":{"data":{"data":{"alias":"findentity","blskey":"4jBpQMFjEabdTHArLHAbidwcaR5o7p4fAuYbfxrtypZtDbsDACj5mZRFVvkXy5F84g7ni6yNMfL5JV1E3qXYsg3fw33Jd3MTRyAGrZZmN3zNEq6WDNT8XQsaDasDUebJaTEmRWVKMZc8BbeipCPBjym2NvfAQyib1ywZZ5B8d7m5XXH","client_ip":"172.31.28.111","client_port":9799,"node_ip":"172.31.46.137","node_port":9700,"services":["VALIDATOR"]},"dest":"5bQhBNkoFKCFAtCxe1vcXBoq6FsnJ3nWKYwUfyVS3129"},"metadata":{"digest":"dcdd47c5bcffd8aeee8b281ed78b57eef5e3ba617f905885c0c7b356f07ab00a","from":"2XCnRcUZJH2JEzotFHTqin","reqId":1525741289118866},"type":"0"},"txnMetadata":{"seqNo":33,"txnTime":1525741289},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"2XCnRcUZJH2JEzotFHTqin","value":"4wTCrfhT5wzBLpEkadt2UP21kPxiPJPWoKUKs3VSGAsi2427aJbTzPMFokSpPukfEHqMikGCeXP95kPZ5eRKazBy"}]},"txn":{"data":{"data":{"alias":"findentity","blskey":"4jBpQMFjEabdTHArLHAbidwcaR5o7p4fAuYbfxrtypZtDbsDACj5mZRFVvkXy5F84g7ni6yNMfL5JV1E3qXYsg3fw33Jd3MTRyAGrZZmN3zNEq6WDNT8XQsaDasDUebJaTEmRWVKMZc8BbeipCPBjym2NvfAQyib1ywZZ5B8d7m5XXH","client_ip":"34.211.203.16","client_port":9799,"node_ip":"34.218.164.50","node_port":9700,"services":["VALIDATOR"]},"dest":"5bQhBNkoFKCFAtCxe1vcXBoq6FsnJ3nWKYwUfyVS3129"},"metadata":{"digest":"0581a913d81b117dc6f5eb67e6faf734f838a9d6dd2f2a550edb620940ed522c","from":"2XCnRcUZJH2JEzotFHTqin","reqId":1525799946176362},"type":"0"},"txnMetadata":{"seqNo":34,"txnTime":1525799946},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"RtMtkBrkHCEDtGXXqRfGoV","value":"2EQyW1W3EVq9Qkr6Dnk1baDeE8vPcDxgPoZdKkxnKYWLdi27NLvzjziUZ2Ckeuw4MqhDdBeafW36DMQo4fDzkjBT"}]},"txn":{"data":{"data":{"alias":"oregon","services":[]},"dest":"4wdqCSyPeiqCbQhjsbP9Xjasm7vuU7ithawkpuoBXCBM"},"metadata":{"digest":"cb346aa8a0234819aa63b5c63cf7928fbbc171f1b3c00e345c24fa3f4f1e9636","from":"RtMtkBrkHCEDtGXXqRfGoV","reqId":1526508235374190101},"type":"0"},"txnMetadata":{"seqNo":35,"txnTime":1526508235},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"YHTFcv8P93i35osabNm99n","value":"2X7YznzieZ1Kc6nzWawdQHG5Z8jV8vNiwHq5BBLn8rY6SGXQCSZhichDp5V7n9he64BKoYxU6YLHuNM2DLSsEvmG"}]},"txn":{"data":{"data":{"alias":"amihan-sovrin","blskey":"14Kn3VBKja9pDCUgoVpMzf4c9rXJpvDsaHrnXkB9WtvNMHPihinjiXUNNuKdVuYiai3iTDe7mjH5o2UjfoKyApdjEgGCKqgDKpH6uzH4ZcUekR5pfe4wC172X5tiBQFScm8Ti1VmqTnUdfvGi4rV4NpBGytPMkZE1qhL1WSDCoi8riW","client_ip":"35.187.226.254","client_port":9702,"node_ip":"35.197.150.130","node_port":9701,"services":["VALIDATOR"]},"dest":"2FZAgVmRC87ZbJXSh6seFi6n7AMfBo9HaCh8HseHi37U"},"metadata":{"digest":"2986a38eaa64cee8ebfa7e5184bc98949a57555bef663ab125f83603371d6591","from":"YHTFcv8P93i35osabNm99n","reqId":1528165772663287},"type":"0"},"txnMetadata":{"seqNo":36,"txnTime":1528165772},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5kJFNauix1UPNtii8g8ahpfmEyJaoAPnFXdeSpJ9Ge3GaaLhWM3Hmr9N8ACdf6Wrs8Yg1pkXLRwmBTuDSvJDe3A"}]},"txn":{"data":{"data":{"alias":"ibm","services":[]},"dest":"Eq7m7GMFKPeq5Ek3HH1PkHxzZ46R9VL1Eube3U9wfjp5"},"metadata":{"digest":"ecd93c53a03d0da0e011973c7c4312e4b5426390930247aa48aca2b70550c2be","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1528236888174137},"type":"0"},"txnMetadata":{"seqNo":37,"txnTime":1528236888},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"PRCWCTWYwGJB7rczzTqBBB","value":"5nJ3vmmFxC1qHjvSWFHLvhqyRtEmxo4Z2SMpuS2U5snEShd1ZMP22eDyd1nDgPJUHRAMuYM519eMoLw4Kj4CqZgf"}]},"txn":{"data":{"data":{"alias":"valNode01","blskey":"24cgjd77KER8uzhxb1c3DbecvrSMQd3nYxBjaJTqLkgBPuAizzoFdXQkdb8LNjgnP27hTHCpLSuatQSE2YYexNXqZDtAUqPMNfsZwAU151kYFfCc6ZWLRzsm4irf4PrS5ZpnSmhT3Ta4c3m12zoTmu3FDxUkJwEBC3ubdNdA8EHcVDz","client_ip":"52.43.138.62","client_port":9702,"node_ip":"52.43.138.62","node_port":9701,"services":["VALIDATOR"]},"dest":"AYQcyJvowniMsxU9P93yzyAPCFUxg7hm1xsXitFoBr6z"},"metadata":{"digest":"5b05dceab82e7288a1003793f0b4ff642284a021b3c5746b5d799b116068428e","from":"PRCWCTWYwGJB7rczzTqBBB","reqId":1528316277953334},"type":"0"},"txnMetadata":{"seqNo":38,"txnTime":1528316278},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"PRCWCTWYwGJB7rczzTqBBB","value":"4FHNA9VCJHMNUssRyaQYFsn6SVQdSddSg2BxRDFp1aurDAMY4o925WCGmoYLeNwMkf34w6nF5NrNHVhXSmPBZUs3"}]},"txn":{"data":{"data":{"alias":"valNode01","blskey":"24cgjd77KER8uzhxb1c3DbecvrSMQd3nYxBjaJTqLkgBPuAizzoFdXQkdb8LNjgnP27hTHCpLSuatQSE2YYexNXqZDtAUqPMNfsZwAU151kYFfCc6ZWLRzsm4irf4PrS5ZpnSmhT3Ta4c3m12zoTmu3FDxUkJwEBC3ubdNdA8EHcVDz","client_ip":"54.214.176.123","client_port":9702,"node_ip":"54.214.176.123","node_port":9701,"services":["VALIDATOR"]},"dest":"AYQcyJvowniMsxU9P93yzyAPCFUxg7hm1xsXitFoBr6z"},"metadata":{"digest":"b2a209bb10c902c01167e24e7f876ea2359356d43772f37cddefe42f5adf8c58","from":"PRCWCTWYwGJB7rczzTqBBB","reqId":1532033264248749},"type":"0"},"txnMetadata":{"seqNo":39,"txnTime":1532033227},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"tkPNgpHinWAPXvkbvnphC54pauknphTCMF5gzG5AHT1EUHJGS3bQnDi7UKNEYKAjujExWYJAHtM5iydk26yatBK"}]},"txn":{"data":{"data":{"alias":"valNode01","services":[]},"dest":"AYQcyJvowniMsxU9P93yzyAPCFUxg7hm1xsXitFoBr6z"},"metadata":{"digest":"a8eebd86b4cc9aa422f1fc3e6950def9498e7f51b1d9a720ba68413e53ccfbe9","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1532035631471362345},"type":"0"},"txnMetadata":{"seqNo":40,"txnTime":1532035631},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"Psfx4mLS23gBvZDoWz336H","value":"5VVAPQH1LaZmJ4XG1kKxnmUXawg9nTVc1kwULndbFR9yk19mT3vaM9DES342cBYmrpXuY8zQwsXHk4kSoBqZvezi"}]},"txn":{"data":{"data":{"alias":"trustscience-validator01","blskey":"2ToF3Pfb78JQ3pFs6mYxtofDnHLTBLP2RKCDfn6eVngN4zh4UiHvz9DuUS2dfTpAeZLqBDkKuU9pYRazeDN9fqWgzk6kgZYf8jeWgra7rk33ZbZjHFz1zQjNMk3eju5n9JNY4AFbUcXCTWCdFoSXF9fEySKuNANPqtFNz92H2VjrySF","client_ip":"54.214.176.123","client_port":9702,"node_ip":"54.214.176.123","node_port":9701,"services":["VALIDATOR"]},"dest":"8Tqj57DbizpjWQCHvybtKNqKFgfw2bjJbPZrhHDoRoND"},"metadata":{"digest":"d2612092e47da9a0bfb8c9367d0149927abe50aca98ed127b8cfd9b5975b290b","from":"Psfx4mLS23gBvZDoWz336H","reqId":1532121668250427244},"type":"0"},"txnMetadata":{"seqNo":41,"txnTime":1532121629},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5gVt2TSEFpJHWN3BwMn1YgcURpxtGfijmXjGu6Cf5CXkwTmTp4dhUN5Ue6WFZU7PcKSrKfu8PvroWPHCiP7AsMvL"}]},"txn":{"data":{"data":{"alias":"amihan-sovrin","services":[]},"dest":"2FZAgVmRC87ZbJXSh6seFi6n7AMfBo9HaCh8HseHi37U"},"metadata":{"digest":"4141b470e5def16f7fde7c3ee5efc02f712bc0e5260a59709385417047516736","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1532979260947795461},"type":"0"},"txnMetadata":{"seqNo":42,"txnTime":1532979261},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"4A2vM1MT3vxNdASCpxgYfzNajpYScAipcRoSyUaRkH9dZ4CmfhHjWogS7VF7WLRNXKUssQVG2DdLrUzuFEckaM6e"}]},"txn":{"data":{"data":{"alias":"findentity","services":[]},"dest":"5bQhBNkoFKCFAtCxe1vcXBoq6FsnJ3nWKYwUfyVS3129"},"metadata":{"digest":"06ee3b89112f96f93df9f91554b5f4ada39ef34dbc4dd198ab7bc60e0d2f0c57","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1532979375523513444},"type":"0"},"txnMetadata":{"seqNo":43,"txnTime":1532979375},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5tqAZojtpBxCaTkLvX6Why6MYyofYTUEt8taHxyXRBPrDugsCZjM5aRxEwXak7qNuUJGt2x9LMfVLdB81rk8yR2e"}]},"txn":{"data":{"data":{"alias":"VeridiumIDC","services":[]},"dest":"58uCeMaEiMHSi8MdEdcgVbpmzMKmiLSYCEz1vxPGJND6"},"metadata":{"digest":"aca82cab749e9d722134d46f0891269fcae59b7fd065fb3155dd632e2f740322","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1532979478035983569},"type":"0"},"txnMetadata":{"seqNo":44,"txnTime":1532979478},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"5wXZxmiE9PJaidic57FXGfTWjJCuEidh3FBRWMBjhg47JsBxJvoCVuGi7LMYYd7Wj5AdqLqLUC6oL7hKbEPmKr3b"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","client_ip":"169.61.131.234","client_port":9702,"node_ip":"169.61.131.234","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"ef3b5b33492990611a2d443d579681be95f504f1f82b4e9b6015418a72b5620d","from":"BD95LAmfVrD3JEwaereykM","reqId":1535389152858873},"type":"0"},"txnMetadata":{"seqNo":45,"txnTime":1535389153},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"PRCWCTWYwGJB7rczzTqBBB","value":"2PrAKjgg1itZrkaaa5YipCgtTKyeczeSrm8HfHUfu6n5raMPmHLCkzoYWcaHUYjMpL2LHrnhPhdG6HhCJgRbQgoj"}]},"txn":{"data":{"data":{"alias":"valNode01","blskey":"24cgjd77KER8uzhxb1c3DbecvrSMQd3nYxBjaJTqLkgBPuAizzoFdXQkdb8LNjgnP27hTHCpLSuatQSE2YYexNXqZDtAUqPMNfsZwAU151kYFfCc6ZWLRzsm4irf4PrS5ZpnSmhT3Ta4c3m12zoTmu3FDxUkJwEBC3ubdNdA8EHcVDz","client_ip":"127.0.0.1","client_port":9702,"node_ip":"127.0.0.1","node_port":9701,"services":[]},"dest":"AYQcyJvowniMsxU9P93yzyAPCFUxg7hm1xsXitFoBr6z"},"metadata":{"digest":"7f244345a273550cf1cbeee4a41a5917fe17947c355917c9c2c235de63d3a2e4","from":"PRCWCTWYwGJB7rczzTqBBB","reqId":1537808479067559676},"type":"0"},"txnMetadata":{"seqNo":46,"txnTime":1537808338},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"Psfx4mLS23gBvZDoWz336H","value":"3rywzxRvJAiFsuFmEayMxKxxsJDeLAWYM5t1Jxw4vNt9RAuh5WwcAUmcD1Un6urHMMBadpkxGVMyTHFca4jBAZTc"}]},"txn":{"data":{"data":{"alias":"trustscience-validator01","blskey":"2ToF3Pfb78JQ3pFs6mYxtofDnHLTBLP2RKCDfn6eVngN4zh4UiHvz9DuUS2dfTpAeZLqBDkKuU9pYRazeDN9fqWgzk6kgZYf8jeWgra7rk33ZbZjHFz1zQjNMk3eju5n9JNY4AFbUcXCTWCdFoSXF9fEySKuNANPqtFNz92H2VjrySF","client_ip":"127.0.0.2","client_port":9702,"node_ip":"127.0.0.2","node_port":9701,"services":[]},"dest":"8Tqj57DbizpjWQCHvybtKNqKFgfw2bjJbPZrhHDoRoND"},"metadata":{"digest":"01bea8a952895652f4ff5fed121fbab75fc5bea0cf4a5b905324efa944a5ba97","from":"Psfx4mLS23gBvZDoWz336H","reqId":1537818353199012189},"type":"0"},"txnMetadata":{"seqNo":47,"txnTime":1537818211},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"MHrp2wUhk1quHK9kGCcQtX","value":"4meKCAvuw588qWkDMy1pXvMtt4X6YK4hiQArsxEJ7wrY84JL9ZAN1efBby4BLZvQcE2Lx7kP9mrhsrAdBGXM8hCK"}]},"txn":{"data":{"data":{"alias":"trustscience-validator02","blskey":"2f8SF5UdftJkr19X7TQxtcy7EiP1MLLxnT4sppJuFfuprEKxdtRq2BbkyRF24Xbdd5tfWkf9MsPBs7aWqrNcoCjbL5hsawUmPy7tjWtZLhLgKYtKxfFtPFJETtTLaaUhyrnjNwYa7GoTBYKMdv72ZL1fjZjo3EK6jx3H6fohvSVK98P","client_ip":"54.214.176.123","client_port":9702,"node_ip":"54.214.176.123","node_port":9701,"services":["VALIDATOR"]},"dest":"2p77huA99n3pmj5hxYapzXMrEgATHAoQX2CkxS4TNya7"},"metadata":{"digest":"30ef842e8ccb04aa4051d12d3a565d921892a7f79254398eb9ab3b8ced265ece","from":"MHrp2wUhk1quHK9kGCcQtX","reqId":1537821923451170792},"type":"0"},"txnMetadata":{"seqNo":48,"txnTime":1537821782},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"2Tk12rX3eU93zqCGe5oeAH7WvudtqwLA9cK2Cg2UbirMhEh2YL7oR68SqMZDhvKDyeRtViLwbhNcfoSARGucm7yo"}]},"txn":{"data":{"data":{"alias":"trustscience-validator02","services":[]},"dest":"2p77huA99n3pmj5hxYapzXMrEgATHAoQX2CkxS4TNya7"},"metadata":{"digest":"8cf0e8b8db40d79a7019f1642de1f85764532b5ee7f6cd3a40872d7317632f3d","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1538150155109252223},"type":"0"},"txnMetadata":{"seqNo":49,"txnTime":1538150957},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"QuCBjYx4CbGCiMcoqQg1y","value":"SkAWnEADFvzxgtnWiEYrdZQivjX587hu1rBq1HgzVBubQAS8NaNcj8FEFYWLgtcYj91wdcNnSrx5aCC8nHRpfAH"}]},"txn":{"data":{"data":{"alias":"xsvalidatorec2irl","blskey":"4ge1yEvjdcV6sDSqbevqPRWq72SgkZqLqfavBXC4LxnYh4QHFpHkrwzMNjpVefvhn1cgejHayXTfTE2Fhpu1grZreUajV36T6sT4BiewAisdEw59mjMxkp9teYDYLQqwPUFPgaGKDbFCUBEaNdAP4E8Q4UFiF13Qo5842pAY13mKC23","blskey_pop":"R5PoEfWvni5BKvy7EbUbwFMQrsgcuzuU1ksxfvySH6FC5jpmisvcHMdVNik6LMvAeSdt6K4sTLrqnaaQCf5aCHkeTcQRgDVR7oFYgyZCkF953m4kSwUM9QHzqWZP89C6GkBx6VPuL1RgPahuBHDJHHiK73xLaEJzzFZtZZxwoWYABH","client_ip":"52.50.114.133","client_port":9702,"node_ip":"52.209.6.196","node_port":9701,"services":["VALIDATOR"]},"dest":"DXn8PUYKZZkq8gC7CZ2PqwECzUs2bpxYiA5TWgoYARa7"},"metadata":{"digest":"c1633443684eed4d621235388d23e6adf0264658bed09e26fd9ac41026fa4dde","from":"QuCBjYx4CbGCiMcoqQg1y","reqId":1540910673107959938},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":50,"txnTime":1540910673},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"FzAaV9Waa1DccDa72qwg13","value":"3q94ydPoBiKq7oBjAt5gTrdLDWagXn2GMGBXa1Shpd8FFqg25tXrkYBDvn1a81rN6E3bc9e9gUJwXNg3CZUBNYG6"}]},"txn":{"data":{"data":{"alias":"vnode1","blskey":"t5jtREu8au2dwFwtH6QWopmTGxu6qmJ3iSnk321yLgeu7mHQRXf2ZCBuez8KCAQvFZGqqAoy2FcYvDGCqQxRCz9qXKgiBtykzxjDjYu87JECwwddnktz5UabPfZmfu6EoDn4rFxvd4myPu2hksb5Z9GT6UeoEYi7Ub3yLFQ3xxaQXc","blskey_pop":"QuHB7tiuFBPQ6zPkwHfMtjzWqXJBLACtfggm7zCRHHgdva18VN4tNg7LUU2FfKGQSLZz1M7oRxhhgJkZLL19aGvaHB2MPtnBWK9Hr8LMiwi95UjX3TVXJri4EvPjQ6UUvHrjZGUFvKQphPyVTMZBJwfkpGAGhpbTQuQpEH7f56m1X5","client_ip":"159.89.118.181","client_port":9797,"node_ip":"206.189.143.34","node_port":9797,"services":["VALIDATOR"]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"6815d516eead933d1163295ac5a1b34ef14fc842d5779a4de23847f5d3652f22","from":"FzAaV9Waa1DccDa72qwg13","reqId":1541014309248416875},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":51,"txnTime":1541014309},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"2Vq2bzfip9c3Zo1b5f9ZEffw1bD3jfWtphJd52Gov6opBcewVQWHcS912J251LTmx1YfNydAYWtL2EYQKuS4EXXm"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","blskey":"2j7jqdynFph7cbSwYgKYfzHKsVcQRfYxCU9AdhTAN8gUV8oFrnuPz348zLX8AMiEt85pKGw97FcACcDTJAGABvwCcXFNXNFGTN2U14JkRcg7yNuHFSWWgmdc1aBQJcJA5ZEtPgq2n47W14L3Y23LUv9E2CLViKUKv6nZqfrEeJi7zUE","blskey_pop":"RHxpCSQFv6Xso4JiEyt9jdTm6J3XUCh1SWy4g6hDsVWYqgrJEeLoXaYBBayB7fi6bDbUPuJVbVYPMA4HNA5eRwagc34gr8JhddgJupLxgzowkKkJa1WEm5vDNwqUfH7JihwwEzUPcmNTjnhGiovhRH4v8Mf1uPyzyS5AHsH8qNbrfZ","client_ip":"194.209.53.115","client_port":9777,"node_ip":"194.209.53.116","node_port":9778,"services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"c94bdafaea83c5668ac61ee754f7de174d90015049a539ddc969a165f6abf871","from":"bPTNiLzWPFHKr7mJGaump","reqId":1541412365363342035},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":52,"txnTime":1541412365},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"NUDC81qrSL5kkiwh4ZmUZ4NepE8sNPLtb3Qii41yiBu1kkudhEBct3Y3TtxJetyfhDmJPiSyWBmBUNxisP4L15t"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","blskey":"2j7jqdynFph7cbSwYgKYfzHKsVcQRfYxCU9AdhTAN8gUV8oFrnuPz348zLX8AMiEt85pKGw97FcACcDTJAGABvwCcXFNXNFGTN2U14JkRcg7yNuHFSWWgmdc1aBQJcJA5ZEtPgq2n47W14L3Y23LUv9E2CLViKUKv6nZqfrEeJi7zUE","blskey_pop":"RHxpCSQFv6Xso4JiEyt9jdTm6J3XUCh1SWy4g6hDsVWYqgrJEeLoXaYBBayB7fi6bDbUPuJVbVYPMA4HNA5eRwagc34gr8JhddgJupLxgzowkKkJa1WEm5vDNwqUfH7JihwwEzUPcmNTjnhGiovhRH4v8Mf1uPyzyS5AHsH8qNbrfZ","client_ip":"194.209.53.116","client_port":9778,"node_ip":"194.209.53.115","node_port":9777,"services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"f7e6c28166e69edb0468869fed131fbca344db0943f020cfb8e0137703401922","from":"bPTNiLzWPFHKr7mJGaump","reqId":1541432813752945620},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":53,"txnTime":1541432814},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5UzHhjZMh4N3w6zSK22RTzb6qFpLijjRgDkCT4XxnJYPYrW7erByfJf2FLkmR4LcdSDrSw4hkFuoH5dkPXpfFZzo"}]},"txn":{"data":{"data":{"alias":"xsvalidatorec2irl","services":[]},"dest":"DXn8PUYKZZkq8gC7CZ2PqwECzUs2bpxYiA5TWgoYARa7"},"metadata":{"digest":"ecdfec24f95a4ef3590f9024f1ce46fa4d8ef2288048c89f18d16822d34f517a","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1541451340102642933},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":54,"txnTime":1541451340},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"3Ygb9RCqsT3SwxpMx2WWrCk9W9pKCpg9qjQkERhKhZuCeEjc1C2YEnf3KtrkCxEVg1xti4gh5kCW97apU53jXBRa"}]},"txn":{"data":{"data":{"alias":"vnode1","services":[]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"e7190621d62f017add97a35214ffe39c2820600e91eb7738ac1b6be14be7f7d2","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1541451384934782442},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":55,"txnTime":1541451385},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"34p6FetGiBaiF9rste9tU4bxjDa6jMhPr8drGFfWMfcDBeHrwu4mMdZBGq3RPpaD7G3E74EkgVNA65mPvTG7s7om"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"c3ec6df9998e376a2b31a4e8bf2f951eaa9779cbfa5b6eed166501f0c0fff6c7","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1541451415119667684},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":56,"txnTime":1541451415},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"FzAaV9Waa1DccDa72qwg13","value":"2GDovjbZyz2rh3rzFpmgqM9TycCxNhbW5esiYXgnkqj43QWz9dwCY5Jv7fSKdmEKK2EiJBuFKt7W8PZZFKyrMuqZ"}]},"txn":{"data":{"data":{"alias":"vnode1","blskey":"t5jtREu8au2dwFwtH6QWopmTGxu6qmJ3iSnk321yLgeu7mHQRXf2ZCBuez8KCAQvFZGqqAoy2FcYvDGCqQxRCz9qXKgiBtykzxjDjYu87JECwwddnktz5UabPfZmfu6EoDn4rFxvd4myPu2hksb5Z9GT6UeoEYi7Ub3yLFQ3xxaQXc","blskey_pop":"QuHB7tiuFBPQ6zPkwHfMtjzWqXJBLACtfggm7zCRHHgdva18VN4tNg7LUU2FfKGQSLZz1M7oRxhhgJkZLL19aGvaHB2MPtnBWK9Hr8LMiwi95UjX3TVXJri4EvPjQ6UUvHrjZGUFvKQphPyVTMZBJwfkpGAGhpbTQuQpEH7f56m1X5","client_ip":"206.189.143.34","client_port":9796,"node_ip":"206.189.143.34","node_port":9797,"services":["VALIDATOR"]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"bf0cdbb6b134fc10fd7797fa24f4354a9e9d70dbc92570d23267e151299b24b4","from":"FzAaV9Waa1DccDa72qwg13","reqId":1541618294089244398},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":57,"txnTime":1541618294},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"Bt44zXEB8HkNHJ8mSGq8Hap4xq6QBo1YECwDscH8aoKocmE8LpDKs9nAwUxu5qrtRNHfj9n1bSeTAq593BT43T6"}]},"txn":{"data":{"data":{"alias":"xsvalidatorec2irl","services":["VALIDATOR"]},"dest":"DXn8PUYKZZkq8gC7CZ2PqwECzUs2bpxYiA5TWgoYARa7"},"metadata":{"digest":"59a07975ea03cca3db85c06f0e12a7095df449c56326f470aab9a5f473c4b41d","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1541715283104974168},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":58,"txnTime":1541715283},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TK4JebQqeqq5t6x2bCwnD7","value":"MC5fG4DowNdB23vHXydtT4f7n1k6wcyZyPA7m3aVZWYfKVT7bwGKWqzmKWGAr1QNm3dkcL2MLVE11ALQsCcFpEt"}]},"txn":{"data":{"data":{"alias":"NodeTwinPeek","blskey":"Jrbf7k1xgkbhfKAmVXqfLLmFieGrxL1f1H6WRBZVB4Rvh8uCHGVoVzMppygH2XPLK4n1cnaBKe7zYxftgMaYXka1HLaScfsVCGqpkSa7d2hzerpcvPQMvo9TCCTP3jWb6uC9kVUHZkVqVvecMDtRkVqr3ZChUAoTM2e4UGmgqvE3Zk","blskey_pop":"RY3ZXV5WoHWMM631ov7ZMWoTX41Cnah4CrwQnXFrPHt49ajB8b5AjnrSDxCb9JEhC8WLVveuQMH7p6FJfoQHRaG2tR9pQLgLCXvbDozYPin4LwVzV3Wh2LNMorAtJgr3PfqxzUmFNEkbiGAbzMdBS1EXbDya9exgrLkrMLuLG1crLw","client_ip":"178.32.102.66","client_port":9782,"node_ip":"87.98.136.246","node_port":9781,"services":["VALIDATOR"]},"dest":"2bDviHYdDiTjyXYXEW92zQHEf1C1QsbFatJ6uSYuYrHh"},"metadata":{"digest":"0d187610d42d46fc90ab873fa1a43132c35d349083dcbf744cf81191ae4a4760","from":"TK4JebQqeqq5t6x2bCwnD7","reqId":1542035094044574100},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":59,"txnTime":1542035094},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6mTar6XRRgYdhswcnq3ybf","value":"UfJqCMUzcX5CfqGouLMSivJ5qm5SqBdsJwKkV9aZHTsmHS3GwcBAur4aUP9VG43praqiqToCQ1jZiMyUYj7wKEw"}]},"txn":{"data":{"data":{"alias":"lab10","blskey":"2Yf73vj1CJBqibWHZTdL4Seygd53dSb4PKJugpeokVx8HdXwqhuEnk2nFBCYmXmFp4RxGprQuKDfGuxYDhKuBysHSCbbYwvoWaXXHYtxvD67Ytw37fQo9Stvgu5nCDwDWD3M8p7fkUF4UQRMqfa8W1tddWXgFr5NPSB13GrjJFcPgAn","blskey_pop":"RBGAkA3X7w1ADtQeYiVZW76uWdwRTdCwamappXLb3sD2iNvVBaSF1jPANo9K99QBe42kEAbJtbwsi4AoDWrjex6tVTQqiSxeAt2kRJpCZTGHfthHzLEnFtZqqksWeXQPRNzeeRagFRyZAQhPuL7wYMiz3a6CNnbgQL7dbMvNBaQsUs","client_ip":"5.9.17.149","client_port":9702,"node_ip":"5.9.17.149","node_port":9701,"services":["VALIDATOR"]},"dest":"7vruXjaKFp2t1WrsMTcVZuNQtmn35yimDrN7THYwcPof"},"metadata":{"digest":"515a3de1e3b1166208d6e2d5c7c020b9d9b56f5fbfb01e9ac1e2da96a8ebacb1","from":"6mTar6XRRgYdhswcnq3ybf","reqId":1542135182121048577},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":60,"txnTime":1542135183},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"2HaQjM7gQXQi4nL2CHLDAM6PaHXUsgQ6SoyHkncrNHQua5WWbcd6DjsB5gagvTAsyD9728xA3npf6c82GNCFqrd3"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"0e549b9dee11241b4b9605b540b7162a84c8fe0a466e30fc029929debb9f6f5f","from":"bPTNiLzWPFHKr7mJGaump","reqId":1542298164808052701},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":61,"txnTime":1542298165},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"wKcykStr9Mj6jbjHUjzjkQXnkz7K3S9GmjgB2kZ7KrWpNDrS3hWWydguhUzFcjHoxBDazdEBKgnu3WH15ytNrxx"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"93b91ab90788049d62246a754432eb2d5f47c8b20e6f7174d57fd42537749c14","from":"bPTNiLzWPFHKr7mJGaump","reqId":1542299088770647926},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":62,"txnTime":1542299089},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KvGE2tKSDuBXEkRc86dL4T","value":"5dwYyoYZu4LRnhdyRFXt7xEFFw2yT8QE7zVKRkD2XswxgsSY1gdnbPDnqBwoUTyUK7MjzeRjuGDJpeBBcHSmH7Wz"}]},"txn":{"data":{"data":{"alias":"trusted_you","blskey":"4Hf3okFu15E52JuH62AD4gNVyBdqg8mP4xzg6bViuYzA3ujCJpC5xCv2afiAgWq5w6ooxhNo4w88jny5je83HvJXUqc1jQNWApzcRr9Zqfz9ipqP3qJv6j8BZJU5VQPfLLfrGRrNG4UFPwHHqXLxcBfFJFqKuBMr9FeQc5LMP6LKhQr","blskey_pop":"RLK3VsTPH8BvpqjBUD1mn21jffYkZdAzEkxt397HcbpHd6zfXE6DbziXrWgVDdofZkuqUhrd7ecwY7yX3rYL182VxfWhvKTDx8VwrPyand6M2DWtR3c3rUwwqBkLJW5dXP2ZrPMnUrukJ88SXVrf4J23jiy9xFTZLAVZnt2Go582aB","client_ip":"51.140.243.125","client_port":9700,"node_ip":"51.140.220.111","node_port":9750,"services":["VALIDATOR"]},"dest":"7wetNy5AJpHfXvhTx4okZokSw5mcrgWgVZ8jJ3WHgrmd"},"metadata":{"digest":"1e72a8499fe7372eb9b3f7876a4bb965801c9ee611d79611afad3355f9133c9b","from":"KvGE2tKSDuBXEkRc86dL4T","reqId":1542323966510001027},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":63,"txnTime":1542323966},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"2LpmtWWx2ykmxYQ9QZ13PoXrtn77ETbjTrBRDmZULykruGVyDWp6Xn3EyrrNuEZVDxjFGKG1tdbcQKhUHKJn6LHA"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"e6a5bb4cd4189ecc6ae0858a32c7df509e81c181bab5cf4f43e72d480ea0f7a0","from":"bPTNiLzWPFHKr7mJGaump","reqId":1542361064125639673},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":64,"txnTime":1542361064},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"uV9yMB5sfwYNRjXfvbD8eajsW16SEfAxE62QVyX2f9YBkr2BBLed9WHE79Ww5v5ycV5mrduAr7rrYZnCNyNvgvv"}]},"txn":{"data":{"data":{"alias":"trusted_you","services":[]},"dest":"7wetNy5AJpHfXvhTx4okZokSw5mcrgWgVZ8jJ3WHgrmd"},"metadata":{"digest":"a915255c0490bd661652aae007d55c88736a9556a283e9087862db948fc01ab0","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1542394857400361543},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":65,"txnTime":1542394857},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"hywDqQdRthvVDSiUXgMueRW621soWAWgZeRFjyzxpvTMBDhxnc36P8PaWYnHsxXzVds57XAcQ3LKfEjZ1UyqvHJ"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"056b23376851dc03079a1c3fea1ec6a37d60d976cef577cb1534bfa5f37509db","from":"bPTNiLzWPFHKr7mJGaump","reqId":1542638324609009633},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":66,"txnTime":1542638324},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TN7Sx7qF9RSmaDcTiFKWzd","value":"4E1w4F5QcM5PxBYYrbJkTDPNwqNnfE9X1nLPVfbfBwFurF2F74GA6BRcPxVoiWsTRMCeWtgZWzHjYcictzv7B7nk"}]},"txn":{"data":{"data":{"alias":"dativa_validator","blskey":"3bYtYhVodD49a3bTK2bZzTAZqt284tKQ2vgXG2arqUyYJRhYnDouhAaQAM7fctC84NvQRG2p1UwVdcdUZPkau5wdJ6fKWwpGo1mM2firHhYtiSZcCAfAbPQhTSSKnsaMnUzacuPC1e81ytr3cXjPobaAg4jJA637N2MGwUWv665o7ae","blskey_pop":"Qm7nQt5HjnaLSpCobPxAH8JG67J6bstaKop2XCsnBELR8tJNm1BYkqTTsj5HVRwXBkMg2vexNQ51B29cp9tSK3zK7ddbhWh8C4cDHoDQgaCHNz61hQxxYMx37yraFLggPF9WCaUckAn4fFxEvLBMV3EdcBZoTecwGPt2g1WhBDjB3M","client_ip":"52.91.89.252","client_port":9799,"node_ip":"35.174.181.186","node_port":9700,"services":["VALIDATOR"]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"58a1d4e0b24778694973e2ff21eca5b9bba33ffb6d918836e1f76b75d1eab56d","from":"TN7Sx7qF9RSmaDcTiFKWzd","reqId":1542652100358243716},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":67,"txnTime":1542652100},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"sYtD5iKjZF1A9rkkY8E2AeHk4B5y81jWiY2BZEsLYy3s4HNQo9YKy5K6X9k1LbscodpMhC4VL4gZ4L7Y9YgvFBs"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"afa88dd032669c15cf2fb1d7f5d921c2325d2d721b6adffc20df7c8e094d4b91","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1542679993748339893},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":68,"txnTime":1542679994},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TN7Sx7qF9RSmaDcTiFKWzd","value":"CRk8NSxyvawVi7LUhDdLgN22L5ZEqMpW5yUqVd9ri623VujthBNW6RWaQEagEJqzj9LtwWG3cRYMZzZ8tW8PFpf"}]},"txn":{"data":{"data":{"alias":"dativa_validator","blskey":"3bYtYhVodD49a3bTK2bZzTAZqt284tKQ2vgXG2arqUyYJRhYnDouhAaQAM7fctC84NvQRG2p1UwVdcdUZPkau5wdJ6fKWwpGo1mM2firHhYtiSZcCAfAbPQhTSSKnsaMnUzacuPC1e81ytr3cXjPobaAg4jJA637N2MGwUWv665o7ae","blskey_pop":"Qm7nQt5HjnaLSpCobPxAH8JG67J6bstaKop2XCsnBELR8tJNm1BYkqTTsj5HVRwXBkMg2vexNQ51B29cp9tSK3zK7ddbhWh8C4cDHoDQgaCHNz61hQxxYMx37yraFLggPF9WCaUckAn4fFxEvLBMV3EdcBZoTecwGPt2g1WhBDjB3M","client_ip":"35.174.181.186","client_port":9799,"node_ip":"35.174.181.186","node_port":9700,"services":["VALIDATOR"]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"c52795b715a91b80167eecf727e15be08c7ea405b32be84ebded577f2bca7dd3","from":"TN7Sx7qF9RSmaDcTiFKWzd","reqId":1542737602414121779},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":69,"txnTime":1542737602},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KvGE2tKSDuBXEkRc86dL4T","value":"57mY4UHLprb4EdgPpnNBiermToDVqkLzJxso2bbsfo4GXomLHssQqjZrZvS1vgDjLgGGKLFpGZJeaKvXyLk1JUNy"}]},"txn":{"data":{"data":{"alias":"trusted_you","services":["VALIDATOR"]},"dest":"7wetNy5AJpHfXvhTx4okZokSw5mcrgWgVZ8jJ3WHgrmd"},"metadata":{"digest":"00477bfb2052242abbb95063d98b5acd1d3ab76e7d1519734e54b07aad6b9e99","from":"KvGE2tKSDuBXEkRc86dL4T","reqId":1542751929819744539},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":70,"txnTime":1542751929},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KvGE2tKSDuBXEkRc86dL4T","value":"td4qZQbCgAxg5jWNUEj5ySAvjN2W2cdywcpYhwrAcyu5H5EbyuyNXDJXLzgCRuTLoNUcKJY1z6GbrFa1rb3c9yU"}]},"txn":{"data":{"data":{"alias":"trusted_you","services":["VALIDATOR"]},"dest":"7wetNy5AJpHfXvhTx4okZokSw5mcrgWgVZ8jJ3WHgrmd"},"metadata":{"digest":"74436d978077d1b2a7325604b305ba71776432f078ae4f0685cd29780466fccd","from":"KvGE2tKSDuBXEkRc86dL4T","reqId":1542754098893802328},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":71,"txnTime":1542754099},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"2odhsxVn68qbBkYcaUWzFpk4KNtrUSh3QM6duqfGwBWN3CPbBYRFfvcYFiQqYZUazipKsWvqHvDsh27iycAadosq"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"a06031660b32d4a6dff612a466d0dad7f0563ac58ca06b2bd11c0f767e0ea25e","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1542820256271098712},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":72,"txnTime":1542820256},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TN7Sx7qF9RSmaDcTiFKWzd","value":"8ubefkYZGbM2F9KeYYhGowKLi27dhss6S8bp8MiirdicFdCZ4PA5qGejbkQhuDfbbRkfxS8ZzreRAg56rkVE5vK"}]},"txn":{"data":{"data":{"alias":"dativa_validator","blskey":"3bYtYhVodD49a3bTK2bZzTAZqt284tKQ2vgXG2arqUyYJRhYnDouhAaQAM7fctC84NvQRG2p1UwVdcdUZPkau5wdJ6fKWwpGo1mM2firHhYtiSZcCAfAbPQhTSSKnsaMnUzacuPC1e81ytr3cXjPobaAg4jJA637N2MGwUWv665o7ae","blskey_pop":"Qm7nQt5HjnaLSpCobPxAH8JG67J6bstaKop2XCsnBELR8tJNm1BYkqTTsj5HVRwXBkMg2vexNQ51B29cp9tSK3zK7ddbhWh8C4cDHoDQgaCHNz61hQxxYMx37yraFLggPF9WCaUckAn4fFxEvLBMV3EdcBZoTecwGPt2g1WhBDjB3M","client_ip":"52.91.89.252","client_port":9799,"node_ip":"35.174.181.186","node_port":9700,"services":["VALIDATOR"]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"62a7363b27f1e10c215c8ac44b0a6d737fd60ad14c4357c6c954d90283f21aab","from":"TN7Sx7qF9RSmaDcTiFKWzd","reqId":1542651103246022659},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":73,"txnTime":1542820263},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5UWP2RqYa8zdejirm5hA7myhrr59jzik9koEjyosJ6U3uHU1ySrKnRsogkXb91sFb6t9Rs9XS7yF6r9wLgW2tQLR"}]},"txn":{"data":{"data":{"alias":"dativa_validator","services":[]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"a6c82dd6a170a73014ce079ce53bcf505bfbee47363a3ea88097cd58f1d38030","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1543358290828986263},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":74,"txnTime":1543359213},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"SYQLd1z2fd6BCcSHsraFbU","value":"5ohoJwSwPD3pGuXhMdvEcYUK3rPPa3HT5SyAgbd4SmHmA1Tgf5zdHef9mNDwR7AKEEgEkUGKNzjAKGTots5oeanF"}]},"txn":{"data":{"data":{"alias":"SovrinNode","blskey":"4ATbikmEcsPinBFgzWk3zKr1HPBg2Qyh4B6dEkR3U8oSauZGJakaHZMDx9LhrmajC5MdSURcLHJig8jqAAvJSkjk2kbpW9m97oWo8jPNw6cDv3bLxmhnkteCrfVvPokbeL4WyFbGZx5VLLrmtzsxodHrEhm6jkZcAhxcA29EzFPKm31","blskey_pop":"RYPJaQyeF6Xjk8jVDbF4gpKdRajTyhDG6VjZeWg93QEMDsWKmTu6CMFQ9SSf4Ao58jySP2Z1LSgKjvW6uJuVm5ZA29Rsdpv7i5y9XktMw14Pa4XH2YJWbpqvZZgteYBbUsFReF3FiSmaHX3JBo7WGWoVmLATxvSxBtPfKKTB2f3KtT","client_ip":"3.16.198.41","client_port":9702,"node_ip":"3.17.50.10","node_port":9701,"services":["VALIDATOR"]},"dest":"GfczFDBo6wCK7bwZA2dtTmEf5xGzZEDeELMP34bS9y1B"},"metadata":{"digest":"2d22f985e30f12bbd0af7dc89be3b82d7254762a212ea865a570063be3a9190c","from":"SYQLd1z2fd6BCcSHsraFbU","reqId":1543447274763531000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":75,"txnTime":1543447275},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"4NFboPNaWijRoMcgQkQFivTjjXaBeDzhQAa93MbAfHp8myBJ1fGnnrnLfyoBMQA1wZhSiAXyeuMiiG8rRhVjbNam"}]},"txn":{"data":{"data":{"alias":"dativa_validator","services":["VALIDATOR"]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"71434b20259474ab9aee98e999a0818e8ac2872043cf5ae9945b307431ce6fbc","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1543513967433499896},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":76,"txnTime":1543513967},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TN7Sx7qF9RSmaDcTiFKWzd","value":"qJLHVvfJiNEexfo7rwxXuNGqhhas1CFY5hxQBrh21dn6oEybkFy41pNVvhU2Qb4eW3j5VyBE65nnFqYi8k6JRRZ"}]},"txn":{"data":{"data":{"alias":"dativa_validator","blskey":"3bYtYhVodD49a3bTK2bZzTAZqt284tKQ2vgXG2arqUyYJRhYnDouhAaQAM7fctC84NvQRG2p1UwVdcdUZPkau5wdJ6fKWwpGo1mM2firHhYtiSZcCAfAbPQhTSSKnsaMnUzacuPC1e81ytr3cXjPobaAg4jJA637N2MGwUWv665o7ae","blskey_pop":"Qm7nQt5HjnaLSpCobPxAH8JG67J6bstaKop2XCsnBELR8tJNm1BYkqTTsj5HVRwXBkMg2vexNQ51B29cp9tSK3zK7ddbhWh8C4cDHoDQgaCHNz61hQxxYMx37yraFLggPF9WCaUckAn4fFxEvLBMV3EdcBZoTecwGPt2g1WhBDjB3M","client_ip":"100.24.186.243","client_port":9799,"node_ip":"35.174.181.186","node_port":9700,"services":["VALIDATOR"]},"dest":"F15n4nPZcnzDJJMNSZK8yNaeodi1PjZyDs83r4KC75hy"},"metadata":{"digest":"47ef7d8fa8fe23a6609710042b27ed83e8ee4520e7c51698ee04419c7b7b0daa","from":"TN7Sx7qF9RSmaDcTiFKWzd","reqId":1543514771626991925},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":77,"txnTime":1543514771},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"326qebvjJFB9dBhhM26n6nqRm1DrjDATdY8JR8r6Wr4snfrmBpasSZATrUarQzAzu3AdZia67zwQsmoepwfRRo7y"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.138","client_port":9702,"node_ip":"169.60.4.138","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"45bf4a0c9d8267ed8cfb37933cdf50ed3eaf23f7bcad6681a65cf55fd0120de0","from":"BD95LAmfVrD3JEwaereykM","reqId":1543518036999212000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":78,"txnTime":1543518052},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"2kHqvEXmgiRcPghcNdGGphFZJdBj4g5CXNsDQkFx15FAprGvmt5DbDC1N4FZQCd7Gi4LzSAn4cQ5eoKsnBmn5UnL"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.138","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"933101316c883ce1ade5c0fd93e226448fc88f38d858788be4986596f7b9be38","from":"BD95LAmfVrD3JEwaereykM","reqId":1543519064797689800},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":79,"txnTime":1543519080},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"zPAr7xh7rcMYkAh3Vd73TFAoX45mtU3eaP57Ke6JPcv6ppMr4iYpafyB1X2DAB5sQsBRn2MVC1BJrfcwdCrDG1U"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.138","client_port":9702,"node_ip":"169.60.4.138","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"2dcd0cac5e059298e5cdfa8cf0081c95b33a6d9a09086909eff9d7b91f05e23a","from":"BD95LAmfVrD3JEwaereykM","reqId":1543519131380345700},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":80,"txnTime":1543519147},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"AWZpBgGbFFCobzuzsqdeZg","value":"35aYZ3mw5UyEZWjKRB6ciL1d3JqZaMmbWCdAp3TtoV5i1YkDN4nRV98bzPqdSeh5sX1atEifuNKSfEWkBVg4qMhW"}]},"txn":{"data":{"data":{"alias":"sparknz","blskey":"34cHakLPF7ZZtRjysMoXT2SFmaqWinh19y2orQ4BPncYsA2J5fkfhtd34jruhjbRWWLpTw92XgCsTrQPPSdUheyqqs2AFZ7QDwKESAxuukV7N6NwWQBEf7i8GTfJaL5vBqqJxDwDNH3j9oLdeMvtTjU8vrnWZLWb6TKjmzD8NrtwF6o","blskey_pop":"Qp3N5anCNnktZFVWpWJQHexcT18j66dXM5cSd6SAsn9uwMAxU8VxVLjDJrRmutVwbR81EzduJfVojMgPfDdHEPxEDFQKjG2EP6qTk7o7HRyts7kaSfkL1f8Dwk8f8tbU5gkaaLrAYGRkXSjnmMPJHXaj6zeeNQRatZJGeRMAG8o8fh","client_ip":"146.171.248.185","client_port":9701,"node_ip":"146.171.248.186","node_port":9701,"services":["VALIDATOR"]},"dest":"DdAqLDrkEW96hcVLsEtf8SrQnUGFK7uMLyHi775kYFVw"},"metadata":{"digest":"5d1e7deda854cda687afab5defd30a1e940eed638dbe7d3c57cb4e326891bd3c","from":"AWZpBgGbFFCobzuzsqdeZg","reqId":1543870622701863717},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":81,"txnTime":1543870623},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"31oppTng8a1BtbZDEo4LNQhF58AMjSvAznR94KxjQkA6BFJg2sgSwzVXWVbEhVL1H4RzaDPjfnHNPWztjDGTUYVX"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"2eb2a01dea5d319f834d1e7a06b8a07258e41f22d4f05422b12f9d5986bc34d5","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1544074481553187626},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":82,"txnTime":1544074482},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"41YVszp9VT9djZsVqswxQTnSRX7BEpv2M7QLvPn6rPUGStbDipB2rxwWxLCzFZRGyNQKRvUi42R2KHrHPfU7Wic7"}]},"txn":{"data":{"data":{"alias":"vnode1","services":[]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"a4f609e0cbf8747f759a746909dbf1e3c9248db7552685ead0405ac72c9e8927","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1544074647137451532},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":83,"txnTime":1544074647},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"2KoQ12pS6MZA3ibexjZ2UqiTS4NiYvT79bwaxzS22cetgwDfqsmvsR7wWLuyXwkSU8HyX8X7NNK6xgfYPBN4mwMt"}]},"txn":{"data":{"data":{"alias":"vnode1","services":["VALIDATOR"]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"92582f39703e76731d5698e5b73e19a8c1ba76f992f91932366607d707bb6a5f","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1544075270586069533},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":84,"txnTime":1544075271},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"32q6n5cXWTbbSafBxY1bS9yXopVjkL6esnq9RNPoWbGgNFRPntZFzZkZRpGf8g9WxJANYgHX5eWRcxtuAjVjuGMY"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"0ba8cb01d0926a2bc950883a1fe1994ac6f1ec3ceded488e0d00fc95f494bfc0","from":"bPTNiLzWPFHKr7mJGaump","reqId":1544110495269165919},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":85,"txnTime":1544110495},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KpBWkKc8yn3iWzjHDDmqBL","value":"5ZWLPJckDRWJc5TJrkpzyBxmsQQBEHfQhSvTKW6NSY2qVroijPMqVByf3tTAUXPfdQ4jGdTSWzS3E4YvvyqTaGf"}]},"txn":{"data":{"data":{"alias":"EBPI-validation-node","blskey":"4XiKTE8hX4d2WVxd85epKmN91wEz9knWXTrEu6Ug9RUb32zaQ4EC6KPmkVzPmE6QAigDaD9soYcKkUVxhzpTFSQuJbSCLUJuNpvDbdMznGw9YoX8n3bBM6bGgPxQzYfRtJGJ1JtWmhViHEF1FhyZxPYqHX1pUbUDd4yKzxJeEabMYDt","blskey_pop":"QkALSfgxL3p3fpswcYJhhaqzi6sYGQNErBSYjqVEuYkuYWPBvnFjNpCrw2GprHS8XcBzss2qeyTXpTRn4PPo3y4MFU61Jwbkbb4Du81Dwh7XUqjiWiV6HvHSdafaeX6P7cGdtcZZAFKsiUWxuosiaeXjk1y9T47ch1hEFSdoh6fBSo","client_ip":"185.242.244.69","client_port":9750,"node_ip":"185.242.244.69","node_port":9700,"services":["VALIDATOR"]},"dest":"j2JLXyTCAMuHSRqZ7eB2JCXSpPniDFUsyT5MJcGAjUG"},"metadata":{"digest":"7ac78f73c3c397596765d3a2b5afbf01096a051c7702d7e29e53573757912d3d","from":"KpBWkKc8yn3iWzjHDDmqBL","reqId":1544725436218772828},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":86,"txnTime":1544725436},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"3TmLPiQF71J954XuFXyPtFKpiECWTL52mCE9AABTCwsFMcx6rforPwsxVKvRjveEuJLv3uucY9ybPnoFfFxGgrKh"}]},"txn":{"data":{"data":{"alias":"EBPI-validation-node","services":[]},"dest":"j2JLXyTCAMuHSRqZ7eB2JCXSpPniDFUsyT5MJcGAjUG"},"metadata":{"digest":"c08be5f00f5976cd163f503430751494dad23d97b16a9c0835e717183039f081","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1544726215993081431},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":87,"txnTime":1544727345},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"5M3i1PbpvEQmTk25EmAY6N","value":"2RTUVnKLx3FGwBQofPD86z4KvgCpN4dbw6Xjvz7Ka9RYdZnydJZ3oE7pZr2Q6V1mZzGqPfAmnW2YEDqozk52THDt"}]},"txn":{"data":{"data":{"alias":"EBPI-validation-node","services":["VALIDATOR"]},"dest":"j2JLXyTCAMuHSRqZ7eB2JCXSpPniDFUsyT5MJcGAjUG"},"metadata":{"digest":"7f6a8dd88af17b623f0f8f28bff0f2188805f9f940b390d9b24b9c0f24956cb2","from":"5M3i1PbpvEQmTk25EmAY6N","reqId":1544733913396713000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":88,"txnTime":1544733914},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"5M3i1PbpvEQmTk25EmAY6N","value":"5hb9wbwvgaYCdwRYUAQvgHkUPzX6F6BhirapR2ni3BAYMGqT9irnJWVMciEdTGZj3rKY2ThAfVtNaMbSJnW6HCyc"}]},"txn":{"data":{"data":{"alias":"EBPI-validation-node","services":[]},"dest":"j2JLXyTCAMuHSRqZ7eB2JCXSpPniDFUsyT5MJcGAjUG"},"metadata":{"digest":"0f52e5c1d547772efd54bfd01d8ec1caa9ba6dce8ca9234ce977008835e4aa85","from":"5M3i1PbpvEQmTk25EmAY6N","reqId":1544734167152848000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":89,"txnTime":1544734168},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"bPTNiLzWPFHKr7mJGaump","value":"5SwN2QC6rG7iuQpwwgNccDRhfNBG3rbu8trV5UFnQwHAw8FG4UfhEMkMSk1XUouAqrzY8xZVxThTFgHBxUYyPURx"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"796c9b3f2b9f78bdd482763e0685e8d6b7f2764d4a3374c30b113a026dec6509","from":"bPTNiLzWPFHKr7mJGaump","reqId":1544781409602174729},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":90,"txnTime":1544781409},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"558b9TUNBbNFeGd7S5qW14uUYdx8tZzQP26UCMBuMbR1KXowJUPSebASQ5yMQWwTu3AoWwRUEH74en9wpMSLKo7P"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":[]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"61352cdff0ee3db0e06ddd2675b04a977efc2ff24ab099e6be4ea37c431f80b4","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1544812283315027137},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":91,"txnTime":1544812284},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"4xgBtM4FFMyfndQgbEb7wz","value":"3vBhqo64dvx2r86WuJKZuVfCtLD64FmWo3K1WY2wY1NYTZ7GZivqHjKi5ALCDyxmorD6U5yeMgFPgQCxCei69Ncx"}]},"txn":{"data":{"data":{"alias":"anonyome","blskey":"497qEgWR2PZx1ZUeCQBq2hTva16CkQZpVPtdG4o3tBFYRzXLQkvrEX7vyRrbov9LQQQriJfRdYZ41C8ju4BjPH77zF34diUeLxrtK1kMGoTdTHinLK1116XUW5GZpj7y7i3Aekxh69rDqZZbbd65JFrD2ZEJoNsHj8HydbVaAjm6wSa","blskey_pop":"QuNLxWz9jWEoFq3gh2DUbNZHBFiK26gQ66URFGGNNbd8WtKY9u8D94k2zL3P4K74Uzp6MmJPMJewAWuzVD7CQJCguzDPYMUtd57J1PX5VoJXp4ynmbDZhJQq4v393fU2YSLkhd6Fhci3nRovvyz3gPW4JbbtipzZGwp3VZPL1qZpf9","client_ip":"13.54.236.56","client_port":9744,"node_ip":"54.66.208.40","node_port":9733,"services":["VALIDATOR"]},"dest":"AM8oxRuxRyKvJoLRtAEBBPMXzpMqTtm9yQDenMkS76JQ"},"metadata":{"digest":"77ab8c07c8a525367353aae971bace52932b713b772dd57e9f6c6688942867d1","from":"4xgBtM4FFMyfndQgbEb7wz","reqId":1545088395580554723},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":92,"txnTime":1545088396},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"4xgBtM4FFMyfndQgbEb7wz","value":"5XCS6eP9z4jmSxCS4h3ai6vPJTaeNMNXttC2UyPFBinMEFQUtiZJFtCCPtY9Dx48wzN8JWuKPv8xnBqZhHb928Ey"}]},"txn":{"data":{"data":{"alias":"anonyome","blskey":"497qEgWR2PZx1ZUeCQBq2hTva16CkQZpVPtdG4o3tBFYRzXLQkvrEX7vyRrbov9LQQQriJfRdYZ41C8ju4BjPH77zF34diUeLxrtK1kMGoTdTHinLK1116XUW5GZpj7y7i3Aekxh69rDqZZbbd65JFrD2ZEJoNsHj8HydbVaAjm6wSa","blskey_pop":"QuNLxWz9jWEoFq3gh2DUbNZHBFiK26gQ66URFGGNNbd8WtKY9u8D94k2zL3P4K74Uzp6MmJPMJewAWuzVD7CQJCguzDPYMUtd57J1PX5VoJXp4ynmbDZhJQq4v393fU2YSLkhd6Fhci3nRovvyz3gPW4JbbtipzZGwp3VZPL1qZpf9","client_ip":"13.54.95.226","client_port":9744,"node_ip":"54.66.208.40","node_port":9733,"services":["VALIDATOR"]},"dest":"AM8oxRuxRyKvJoLRtAEBBPMXzpMqTtm9yQDenMkS76JQ"},"metadata":{"digest":"09742193d02bfd0a16e1a18f71e0cba235cd6872f018398f12945274f3117e65","from":"4xgBtM4FFMyfndQgbEb7wz","reqId":1545089703401067519},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":93,"txnTime":1545089703},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"e3SjQWaGqcQsq8EkBrCm7RTZUq6oB6LFF88w2gZ9h3xPES9AMioygTTYiRuLkgTtjLocHiYV1UjosX6ybGH7Y9F"}]},"txn":{"data":{"data":{"alias":"sovrin.sicpa.com","services":["VALIDATOR"]},"dest":"AcaN2zJ1vkQyEvmi2EyUMLzvzczQRvarNPjR2CbtNFAX"},"metadata":{"digest":"f9d4f83b612a218f58cb2aa50face3f7cb0ce488e6b37775d14f1ac85b68e6ce","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1545149889900994815},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":94,"txnTime":1545149890},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"5M3i1PbpvEQmTk25EmAY6N","value":"4ijAWFcxfZHB2M169arvZMudfZtwUEjpfhHVKsFDx9TzAVhEU8jwgj8ZXmVU2bFa3rLzBPTW3MrT43KyTCj6Y6Pu"}]},"txn":{"data":{"data":{"alias":"NodeTwinPeek","services":[]},"dest":"2bDviHYdDiTjyXYXEW92zQHEf1C1QsbFatJ6uSYuYrHh"},"metadata":{"digest":"b5f9a593b5368b30899793f12e4cec879b232c49746da332a53fe4c83cc94b41","from":"5M3i1PbpvEQmTk25EmAY6N","reqId":1546646026772409000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":95,"txnTime":1546646036},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"TK4JebQqeqq5t6x2bCwnD7","value":"2v25oRF7GET3wQZ7Q9TZqdMNVdaJAyR5rmsw3DGkyQkHw9HtyNHkq57EUV9K4Uf75gnyHhRQ9hxau2XJC6SNPa2C"}]},"txn":{"data":{"data":{"alias":"NodeTwinPeek","services":["VALIDATOR"]},"dest":"2bDviHYdDiTjyXYXEW92zQHEf1C1QsbFatJ6uSYuYrHh"},"metadata":{"digest":"f1402734f5bbc90db2dfa3db65d6cdc3ab839eadf13c1a7e16d769507e436696","from":"TK4JebQqeqq5t6x2bCwnD7","reqId":1546880167291826800},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":96,"txnTime":1546880167},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"4RvNoSPKWa1whZRGjpGEq5UDKQUVd3h1m5aRr4xThoFyCR7D2HiszbPGRJH9hhD8V4Sh2cERksU85fyHpW1BUZDz"}]},"txn":{"data":{"data":{"alias":"ibmTest","services":[]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"d847f4140dbc101029081cd2abecc0f32465e310180917e12736d9f102dd2c6b","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1547489538349798313},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":97,"txnTime":1547489539},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5gCEwT2NHiUcuD53F6xW6vp6CnFabbk2kXoLLxfrUHqnjqMQDNwuSU6fm32aYFC2Z7KMjfs9jKfqPLEtwPFWASqi"}]},"txn":{"data":{"data":{"alias":"ibmTest","services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"e92c1fecf19b469d484bad7b3b2115b5e88df9c695afca551c5e1f3422f253f7","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1547497859538640883},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":98,"txnTime":1547497859},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"3Pcohi5dAcVz9k8hryvXn8x6ddGob5hWYzmHoeZdDi628Asc4Au1rmsMjZasvLPqj69MN1VfbE4kTC9oESUPbAeC"}]},"txn":{"data":{"data":{"alias":"ibmTest","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"56af49cfcb420ab39ecee2912fe3acaa2c0e9fcf4bc6dfdf783210cda55495eb","from":"BD95LAmfVrD3JEwaereykM","reqId":1547498794878924300},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":99,"txnTime":1547498795},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"46AVXqBesadhJLZvoRmcyPDDePE9Hi5DahjPC82fhw7irQnVXbvwNhqB7HXfCxtu8w8VifPhEcfihZcuNKBg995B"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":[]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"a1412abace57327ba53e022db6a977e198a6134fec1dc4a17050de981f165845","from":"BD95LAmfVrD3JEwaereykM","reqId":1547500122390156800},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":100,"txnTime":1547500123},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"3yv5pLUCBnbC95FRb2qVrCtxRVBWB9ySRKkTxJ1M4pdTsXZPd3BHkDYTDqms9e3zPNtK5r8RQcg2ws6c5W4Nmy95"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"1f584ac4cc88cb57ad5d9005e4f5f17156b70aad80f302b642289e4840076f74","from":"BD95LAmfVrD3JEwaereykM","reqId":1547500156383195400},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":101,"txnTime":1547500157},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"3f1sTShBSY8s7axeGypUxfY8a4V19kYkkFPyGygGGveJ2KDNVSEtKJHbLQNkaxk1nJnKudWQGKzCS2AdjBG766rw"}]},"txn":{"data":{"data":{"alias":"ibmTest","services":[]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"03f65d7a7bc664f38199f4d34f6c8dd3fbeb5ad8744753b6a3f92def6d148ace","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1547500445740297593},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":102,"txnTime":1547500446},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"5zo62TywH6pZij5L3QU9zmjEmAf7czZDJnyF3zFHXhqcGfGYLWCeUuFqJ7bVMc8Bnfx2XxkYY4UtwZ6oz3HwrqdL"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"da6a5f92b960682e1c372ac18d3287e8a9db6e64cd5c07855a2cd95d5c13263b","from":"BD95LAmfVrD3JEwaereykM","reqId":1547500485921720200},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":103,"txnTime":1547500486},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"ftEtDcapN4TLHkeMDzvf4iQcrtAJvZx1VvvHnhDxqPxTcRVXWXLrQxNZWEjnjFet7ZNtsAQT6MmYHhZGztqrAVY"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.138","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"15e71658452369fb383b2887eb9c4bf3833a3df8e6c48138de55730e086c0e6e","from":"BD95LAmfVrD3JEwaereykM","reqId":1547501596782347800},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":104,"txnTime":1547501597},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"7sTtbnHKD3zyosb1dFQAex","value":"23WRyTk9B2hPHXTMzFnjxJZZUhfsn9DLBbep5fEQqm6eTqYHiGrriTP9PbPLpVF1BHWsygVsVyr1jSQjr3VE6pKy"}]},"txn":{"data":{"data":{"alias":"regioit01","blskey":"RVjJZ8FJDMjFS6hTNVSXMx7S4BfQeX49CLTXvkHWvVQ4LRNBCsWAjwWFcDUzmdA2JA7hMRi9qvjh8vmPDahLeogJyWEByQFdJ4LnFo2NkDAfQwExHjLGXdawHPQjTduRrWYERpjwS9jFs3FXih6FcQRZb9mKrSW1tguSF7qGSdiNmV","blskey_pop":"R2v3TpVYmWpifxideMdW4EQo36w36VsppcDB44JNDxxCREiUZqrBj9RChgu6RRmHjj6SLchea5L515iadvA6STeQYh8tjMF87KH8s6RE5JAQagzX67X2fbpzai6A5Vq6w5wxLPJTKV4P9rkjxnH5Y6R1ReaVcKzZYk6j583WSUJZdZ","client_ip":"91.102.136.180","client_port":9700,"node_ip":"91.102.136.179","node_port":9701,"services":["VALIDATOR"]},"dest":"AQoViW7aucuvi8SC9QWur6u14ppmVvtMvduigy51NeCv"},"metadata":{"digest":"acd16644af0c402f1cce381e9dffcdd138d3d5098c54ac5c7d126be8b2923e50","from":"7sTtbnHKD3zyosb1dFQAex","reqId":1547571055916476853},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":105,"txnTime":1547571053},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"HS2VPUmJ1vJQHCjfH1w42H","value":"5htNrJWGu15CpxU7ocHYRkGQUygcaV41jcRmK9a1rvGoG59qWMJYpc4GvZeh2FttfobbxMZJfB57sggLhFS95y9S"}]},"txn":{"data":{"data":{"alias":"Absa","blskey":"31k6SUK7otXf9MXDgfGRb9N37z8xcZbdRmegNiK5k97sdfNvTSfKeEqSTxBqe7qqNa4ueYKDjW2MYgWBwYF1ohh5GzjUndykwttpffbkDGCrQAaUTJoBi9rJvrwCkkQeLKpoWTMkV6gYp1AiJkhKYiGhtCCAA3R5grGUpcMzDXqzt8R","blskey_pop":"QngATEwvD1MHDL4odeiMqg9AYp9emVmRDSQMeKwFXdtthR9x5tDvKrWFT1gZPECuaRWtEmQBQDzSxebiy5jZJ6DwNbP4EfLtKvHrmHUAVfXKvrzHJiwbeg56oXdyi84xsNLKKZrhhr8hEgpNN1F1FXHfJUDWNyMU3w81peiLLAkYrb","client_ip":"63.33.176.61","client_port":9702,"node_ip":"63.33.176.61","node_port":9701,"services":["VALIDATOR"]},"dest":"BUKQ26j7hqkW2bdQsoxHACyS1n7gCrHeYxLR4yaoWCr"},"metadata":{"digest":"31b150874511509e4ac355e37c9ce7f649fb12bc48a2cf9c591b0f390b076b67","from":"HS2VPUmJ1vJQHCjfH1w42H","reqId":1547574103955392127},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":106,"txnTime":1547574106},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"YTsBtQz1PgjHNNPsGa7i7i","value":"JJD9qYZF7z8nNqP2sAf1ER3HB9rH7FQBhPeVUjyaf3G9TsBUQCv3XVTgV9eGXke5tA7nmDHaQwhFhVDMfpZVq1E"}]},"txn":{"data":{"data":{"alias":"VALIDATOR1","blskey":"uSpMXhzYzgkShiEeWoSrKar8g2iCsg8KL9XaSSs9HNJt5MSCXKjPZtyHNN7KtuLi1ThxKZc8ZUUtjh9uz2ApXZU41PHLX4RsMEGdfMJf3FsPRP8RkodRtqXuxmTzKEvJJ4XihrgHCL85QBpBkmp2u7YioLqwhAxyDmFRLjFnHx4cwr","blskey_pop":"RHFFZHGBUFAkfYQr9nzn2FX2ZQn6u4FmRyfUTMC6DnVfBF7ogJz2oSS4vpi2MitFTwWCofZsB3g58wSPZA4PGAPR8KXjHQvEkB5MS3NDnAgjje1E8fX9XC41qhEJ1kdC68GtgTgJSJRfkmH9c3jjb8rhQskod6N8swtTpqwV4CGgoq","client_ip":"54.180.62.29","client_port":9798,"node_ip":"54.180.16.51","node_port":9799,"services":["VALIDATOR"]},"dest":"99UgWzvjVvhDHfd16V62VFdQEKn8Vk5fuMP2t1d6xx4w"},"metadata":{"digest":"d28d9fab163bd72cf7fbc254ca6b7b495101439870a064ceefd2b18f1d4417ad","from":"YTsBtQz1PgjHNNPsGa7i7i","reqId":1547599663946425568},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":107,"txnTime":1547599664},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KSZhtVyy7yQGxHLq8pxQwX","value":"2tHAgZ2A32YBJzLBWbXcCCfEYjRAfUeswRQQkrfttwk8Av1zvwHyfqQUfQKHKtTaQc1UeUjd7Z1axgEQf9LQqTCS"}]},"txn":{"data":{"data":{"alias":"swisscom","blskey":"2Y6bbfnx22jRKT6vDFq6kJayzqrU3N6hBGvqaepepgQHrC8KyfjwsnbAxr6NAySdFq81SopX5is2nc33d2Kei2jXGjktA7VgT3JPaSQxi6cb1UVKA3taVaqPXdbQvedkkGrgdGuHJgz2Tz5yZQertL2YiqUmKjPn78vSJWF3b6BXQ4B","blskey_pop":"QqARZRNUxwb9DWJW66DAifJNMCeWo2r3Mxy2HPrDEEiFvhba7FiSxQ8czJoVa2r58GwvT9Srvh2tifKUjMsnwt2o1GioMDpTS42MyENa6tvZuGnnsErVHekH5xccnGRe4zVLd3MAmzha8RDL5pMZeLzRw5rDpQLSnJQJKrQzqLho2X","client_ip":"164.128.162.43","client_port":9702,"node_ip":"164.128.162.42","node_port":9701,"services":["VALIDATOR"]},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"b582d138c5f92a84059eccae5c624392fc89899e02b559dfe5521b25230b10f6","from":"KSZhtVyy7yQGxHLq8pxQwX","reqId":1547651916293792395},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":108,"txnTime":1547651916},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KSZhtVyy7yQGxHLq8pxQwX","value":"2eF3euLofexGmoBY8BQutqNkPEGhWvomVQZK1PFs9iKRzUEkZPXAStxG8wuh59DRUcGTX3piZUQYPk6Cb7Dc3cCY"}]},"txn":{"data":{"data":{"alias":"swisscom","blskey":"2Y6bbfnx22jRKT6vDFq6kJayzqrU3N6hBGvqaepepgQHrC8KyfjwsnbAxr6NAySdFq81SopX5is2nc33d2Kei2jXGjktA7VgT3JPaSQxi6cb1UVKA3taVaqPXdbQvedkkGrgdGuHJgz2Tz5yZQertL2YiqUmKjPn78vSJWF3b6BXQ4B","blskey_pop":"QqARZRNUxwb9DWJW66DAifJNMCeWo2r3Mxy2HPrDEEiFvhba7FiSxQ8czJoVa2r58GwvT9Srvh2tifKUjMsnwt2o1GioMDpTS42MyENa6tvZuGnnsErVHekH5xccnGRe4zVLd3MAmzha8RDL5pMZeLzRw5rDpQLSnJQJKrQzqLho2X","client_ip":"164.128.162.42","client_port":9702,"node_ip":"164.128.162.43","node_port":9701,"services":["VALIDATOR"]},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"23c08c4db5ad3ca27e2fd38b3015525d3a76b41ba63c2fb82e2a15f67ca717fb","from":"KSZhtVyy7yQGxHLq8pxQwX","reqId":1547653252643574766},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":109,"txnTime":1547653252},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KSZhtVyy7yQGxHLq8pxQwX","value":"mUhTwVA6XGHF82XJj4eEa5xpaK6qCFF2bwLHsVt4pnfxdVxTMYHJBVHaQfbYALSuDQfdrgktLKv6Goo8PRzvANc"}]},"txn":{"data":{"data":{"alias":"swisscom","services":[]},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"60ac2ccf34f393880b544c5fab223e5daf36a5fd92565556d22d589be6d2d9d1","from":"KSZhtVyy7yQGxHLq8pxQwX","reqId":1547656208364157351},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":110,"txnTime":1547656208},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"24goM7gvgMmhYs7VSd4TJtmUinGTySwYiB59ViLvpbQ9WV4TJZr4R9q36wX7nKRwu9aPBXiJSiEU33ozREQd3SUt"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.139","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"2df2c048ffd27254d4abe5face1ca7984d058a819d791d15a11dfe6dc0885cb3","from":"BD95LAmfVrD3JEwaereykM","reqId":1547499944042787200},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":111,"txnTime":1547762408},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KSZhtVyy7yQGxHLq8pxQwX","value":"3dz2LU41uRHr4jByQMQPN8UNbTixsMWnRkCLFBGFLH91NsFkC4hYvTmS1fSqAZWmabMwNyXGWTgCVce2axC6R52M"}]},"txn":{"data":{"data":{"alias":"swisscom","blskey":"2Y6bbfnx22jRKT6vDFq6kJayzqrU3N6hBGvqaepepgQHrC8KyfjwsnbAxr6NAySdFq81SopX5is2nc33d2Kei2jXGjktA7VgT3JPaSQxi6cb1UVKA3taVaqPXdbQvedkkGrgdGuHJgz2Tz5yZQertL2YiqUmKjPn78vSJWF3b6BXQ4B","blskey_pop":"QqARZRNUxwb9DWJW66DAifJNMCeWo2r3Mxy2HPrDEEiFvhba7FiSxQ8czJoVa2r58GwvT9Srvh2tifKUjMsnwt2o1GioMDpTS42MyENa6tvZuGnnsErVHekH5xccnGRe4zVLd3MAmzha8RDL5pMZeLzRw5rDpQLSnJQJKrQzqLho2X","client_ip":"164.128.162.43","client_port":9702,"node_ip":"164.128.162.42","node_port":9701,"services":["VALIDATOR"]},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"a81375c6b2e43e5ed4d44d565901e33066d77785798ddd93ed2726bea6930c4f","from":"KSZhtVyy7yQGxHLq8pxQwX","reqId":1547652168940154534},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":112,"txnTime":1547762409},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"554joew9xsZWAuwzfgK5CvAgw3hapNcET6Z7qEnVXBNBe23c9TCeAWeRoM9GYDpEypkZcQFwiQcV3jRJXq3wiFPF"}]},"txn":{"data":{"data":{"alias":"swisscom","services":[]},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"0a4a47a4080c3899997cc6e71e78d1e3ffd675e13d95908c9a0b122f2dd0dc8d","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1548175587215323240},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":113,"txnTime":1548175587},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"nLE6kCvr6FwEBvZ8p6aw58DMwFSf7Q5CcEEyA17b4HHQMpZuYdwbpz5w2kuiSac4w9qRnPGB8ouLndmzMggFHi1"}]},"txn":{"data":{"data":{"alias":"EBPI-validation-node","services":["VALIDATOR"]},"dest":"j2JLXyTCAMuHSRqZ7eB2JCXSpPniDFUsyT5MJcGAjUG"},"metadata":{"digest":"03c6e7bb56d6a2870bc9cc7a8e3e4c226864faf1db51368047e9e92d38697138","from":"6feBTywcmJUriqqnGc1zSJ","reqId":1548184180516141458},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":114,"txnTime":1548184181},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"3pAYVxsFgE9u41tsucPUW4Jmy2LFQLKeNNiYcVi4MTm57GyheA5cVzbzLKq2wA4NABSd4EqLf2cCwmzZwUF2iAkE"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.138","client_port":9702,"node_ip":"169.60.4.138","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"be3db7c6f4526eaafece89054a366726ff34fb6d93943b847222fe5660454323","from":"BD95LAmfVrD3JEwaereykM","reqId":1548190805953640600},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":115,"txnTime":1548190806},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"2rn9DmGEeqU4V41MbGThxEuvdZ6qLHsVDeYqJDnpDayCWVhdxXERAfKokcxrXVHPA1Q2gyekZ3jMSuLaFua1jCwD"}]},"txn":{"data":{"data":{"alias":"ibmTest","blskey":"232Z7DQcjp5NPVZyzR6WWH9w9829F4NPBz87sx2LHZBnv2xntpaixyUc7J5hUtwnYgL7HyEZsf3Wgtdr5sGua7jhpJzixxtR2p4KoRZ48i62wA9Y5mJ4FmXBg3GxMwbegc2Nmqg33CGjB8cDGUZwR1jBERdZdsi3Y4CL9e9NsBqUu5C","blskey_pop":"RKL3Tji4ZKCsfBLaSAsnvDBn2TvmkkSSEKy2zDk467aXZFG4fpEV8t89w2FXGeU2vLHV1ppYpfefDoi1Qjgm7vxEPdEzys3ZxGJea9FXG78sQPurLF1XZtK1g6DV5L2YAyV3qC1pyGGibJvc5RNS6kdbihSwkpZnVRDwCr3Pk5DPiX","client_ip":"169.60.4.139","client_port":9702,"node_ip":"169.60.4.138","node_port":9701,"services":["VALIDATOR"]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"16bcd5c9ea536ed306bd414a3797076156c7e143ddcef630e95ee5b2a7d236d5","from":"BD95LAmfVrD3JEwaereykM","reqId":1548191738654080100},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":116,"txnTime":1548191739},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"KSZhtVyy7yQGxHLq8pxQwX","value":"2SedyJRXPkF7yyCHkvEhGeyZ34W9MXrH5AVcFFgQJ4tbsWpF9gKvYL6gawbuvAE8W2FqkKoufWFf8kgxouaZV89v"}]},"txn":{"data":{"data":{"alias":"swisscom","client_ip":"127.0.0.3","client_port":9702,"node_ip":"127.0.0.3","node_port":9701},"dest":"58b3Fy45qjcBfVtEt2Zi1MgiRzX9PPmj68FwD143SuWQ"},"metadata":{"digest":"92c8ec4f3f0aaf806b18b74afb1e8e52d06d5096b9e0c3b3996af0dfc4bf627b","from":"KSZhtVyy7yQGxHLq8pxQwX","reqId":1548781193697645899},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":117,"txnTime":1548781193},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"HMT5rCkqvjcjZZHQFvQtsX","value":"5QNoTkHzevZ328EMXUTzWWveowZE1v4bfdhxaKfNGUvuUk4ympwnkzCPRFnz57xrvouC4A2rtd2T3vj4opCgQbMB"}]},"txn":{"data":{"data":{"alias":"Swisscom","blskey":"2Dys6A6wihGsazQLKe2hN8kHQ17u9Hix9pZ2uTBtuvMSLMaaWZrMnbXYoHcv7iDc58mGBnGEA335gXN8Y1Dntjd3YgeDYS7ffpxGHDuvMzuRpD2iktMCzUCRmsSMim32vURLkPTHs8Pg2fgg4vGcsmAeGk4tFVaCVC6gASgNU9JqDKX","blskey_pop":"RZ31hdcUW3zcD2XMTqvazNcX2YxwY4HSWah9tDzTW6A7G2uhzVoy9UTqzdrGHgZ183VoNKw1EeZvB6kRZYFLckwnW8pKT7r1GS4gUPPcnQxAtogy2bMnQQ8S4ZY5e7WYn2URJ2GF1TSGnzUzqu4iQZWiMeFsBc1CVH98A9PGmYQ3z7","client_ip":"164.128.162.43","client_port":9702,"node_ip":"164.128.162.42","node_port":9701,"services":["VALIDATOR"]},"dest":"B3x2KTn46sZmajraNm9oh6EUWnZWC5yQTrJAUifwDWRV"},"metadata":{"digest":"0ad6663c3841906ffafd51a467d7c43480d912551fbd98e9cd415e1571344455","from":"HMT5rCkqvjcjZZHQFvQtsX","reqId":1548781315218601114},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":118,"txnTime":1548781315},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"G4tfohtFDtmQJAEg6Pwgg6","value":"3oR6cXp4AbetwhgHacWjnF7LhnS8q8kVzA4nqvsvvxKzDEbiFSEF4UEU5BFiiAxtXg9nm6xk5X496v6JHETYdYjU"}]},"txn":{"data":{"data":{"alias":"NECValidator","blskey":"4UVe2Ryi4oPiECy8jxEHrXbNKbyjBjADotdD7ibRfpkfHjkD7UnBVcJKq8NFFf3rmGb8G3i7hGvZw8dUyQvcx67uAKNvYA3QMc3AL77aFNXHjiU5HdEBhwvq9qjs3BWZGoPyRfxun7EjMohiHvGTiZUxAzZD3R8qpaYaJ7DUdC3k24Y","blskey_pop":"QkN8FyGfub1yoMMzHhDBdrRyfKyrqX8kuiA6dXfAM3ghMGq1wWGbA8VuuCf4eWtxZvaD8iz9shC5zJn2C6pzo3BpmmP2bufsAGVWcNhTqxKPfXHds2JC4DagX7BV5mDfTriv4mF4EJD8PbwVgGvHzGFj4FuGTx3BnA9oay3rGxf1m9","client_ip":"52.69.239.67","client_port":9702,"node_ip":"13.230.94.222","node_port":9701,"services":["VALIDATOR"]},"dest":"BLu5t8JVbpHrRrocSx1HtMqJC8xruDLisaYZMZverkBs"},"metadata":{"digest":"3e03ca15f515f8fce2287600d02ef5dee412bc2874b836b8a5831ef9cb526701","from":"G4tfohtFDtmQJAEg6Pwgg6","reqId":1548808410060957151},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":119,"txnTime":1548808410},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"Kv2YdE5KGgdruMGW6p5w4b","value":"2Fm6QXXRYDhh34H7xRYFxHJQyHymyimPBJotwKzGUWJBGEcWPdYUhUFk2VDp9JyYhkapDchyhtS7VxjvQnHc6yR1"}]},"txn":{"data":{"data":{"alias":"xsvalidatorec2irl","services":[]},"dest":"DXn8PUYKZZkq8gC7CZ2PqwECzUs2bpxYiA5TWgoYARa7"},"metadata":{"digest":"e5b3e73768c7698aafa3a9679d2b91b58be5b6a4f7e5b9669c27838b94e3cd27","from":"Kv2YdE5KGgdruMGW6p5w4b","reqId":1551902688751838000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":120,"txnTime":1551902689},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"5M3i1PbpvEQmTk25EmAY6N","value":"5Rkz1NJxoU8rjaSeXHb77XrjD1Gedqm6ZxqN89YEtnajxztxcw5iKEBLnFg34in2ta2mkAdkLSBPEQbArPKFyG5f"}]},"txn":{"data":{"data":{"alias":"sparknz","services":[]},"dest":"DdAqLDrkEW96hcVLsEtf8SrQnUGFK7uMLyHi775kYFVw"},"metadata":{"digest":"34c208f6f214e973d926473b807e1220dd5f4167685101baa0fdde25da577054","from":"5M3i1PbpvEQmTk25EmAY6N","reqId":1551981105839004000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":121,"txnTime":1551981106},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"5M3i1PbpvEQmTk25EmAY6N","value":"29EBhTyCNsXJ9rruhPUfY5DLoXW5JiyZy7eGNxZ649sYefMZmJdpf8dnd2LxkCNswD9oGWPXbiRZbGfCEXc35Zkf"}]},"txn":{"data":{"data":{"alias":"vnode1","services":[]},"dest":"9Aj2LjQ2fwszJRSdZqg53q5e6ayScmtpeZyPGgKDswT8"},"metadata":{"digest":"df690f4b4bd3897bbd321563ef5fbf4fdaba2b095546b19d5cc7840f309871a4","from":"5M3i1PbpvEQmTk25EmAY6N","reqId":1552398621045487000},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":122,"txnTime":1552398621},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"HS2VPUmJ1vJQHCjfH1w42H","value":"58FmzCHRRYsiyFE1NQht7Y6n5LLNMmxdVQkBnfBrpmmWHjgLWWjxSK3BwsJW1e2BHhoajGRBZdCSpMHPvwKDuXtU"}]},"txn":{"data":{"data":{"alias":"Absa","client_ip":"99.80.22.248","client_port":9702,"node_ip":"63.33.176.61","node_port":9701,"services":["VALIDATOR"]},"dest":"BUKQ26j7hqkW2bdQsoxHACyS1n7gCrHeYxLR4yaoWCr"},"metadata":{"digest":"96a1552ad574113e94baac125f9032c97e0a60abb5498ac43d2a12a85c0460fb","from":"HS2VPUmJ1vJQHCjfH1w42H","reqId":1552408626878108613},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":123,"txnTime":1552476998},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"BD95LAmfVrD3JEwaereykM","value":"4GCt8VdmfVFJtufoKD6HJBiiQTR6ujiK4bLn2NmdcQucKQNKD3Jr3McMAkwqnjDcduDdQW6AURLeSDyFBDqzZTfv"}]},"txn":{"data":{"data":{"alias":"ibmTest","services":[]},"dest":"7mcctKwaBjyzAbNPS8ix1LTNxex4JchkyLvjYfw2XexR"},"metadata":{"digest":"29a7d65b74832fc91178e60089521bf0212b48a05d7fc35be8841eeda8dc8f88","from":"BD95LAmfVrD3JEwaereykM","reqId":1552662332849012735},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":124,"txnTime":1552662333},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"c5GFhSHNMdaqjSyzQeaMa","value":"2ZSByqsx5w6rpQm6FbKt1scnUUu1VSbbwXBMoKcDCkyrdsm9yrgAKsR8myTW1z4EFt91zAFUJPoUaHnAd6oyBgin"}]},"txn":{"data":{"data":{"alias":"cynjanode","blskey":"4oMnfEbqH5fuLGQQSWLZA7L1y8D4zL232t6QiJhspA5GsKJAextNa3oyr1MK46byzax4EmyoyS74YkLD5ri6dJmRXFnKPZ9E4q8UTWFBRdmpwGiZXni9HXL8twBHhfnNE8vXirQLvzQ2gcSBRzdGHanPBwKnvqjoeNGfDfM1kT22pks","blskey_pop":"RFvdDmi1a8b1VYA8eRtTs9cC2dmhK8b2P7k9VWcPpPKwupyTQr2amdbq4GcbASyML6RXxAvZRuj2EJH3KabkT3hVPM8g1Wn1VYY2761pwCjcHfN1E2G2Nwe5sdwRqxvV965aYi9595SBVWWabx6bcJ9S5EDqKevrUnZ9nsG7xfoWEB","client_ip":"3.17.103.221","client_port":9702,"node_ip":"3.17.215.226","node_port":9701,"services":["VALIDATOR"]},"dest":"C8H9SzkM6NrfYB1jD6dMCmdBKXvcCgGZpvwD47xGdJFQ"},"metadata":{"digest":"eac8e6015ba1eb3f1b294f9c53e54bc79d27b0a690ab358bd826668999f9cc4a","from":"c5GFhSHNMdaqjSyzQeaMa","reqId":1552676534057365965},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":125,"txnTime":1552676534},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6aAJEoijPh3yqeDe9JCqRg","value":"2QzZSa4ComF5hNzGYp2KxtrAu57tstBk7jZrhv6CK95C36KL3Naif5hPJ2egD4ANjAo7VDyaSFks8fANFjFYTowH"}]},"txn":{"data":{"data":{"alias":"DigiCert-Node","blskey":"34ZnAnof88aahn2jShZqUPyvWXjM4pdjBukbH44BXAyZGcY2rivuMLHwCPZGGRf7JAVx6zGdnQiFziqwj8ndtGghht67H2DqHhoxxeCvgxv5aMAVW57z8ekGyMQXq1UWYEYgjFqY2Fi9bCTkGb4ywn4JEFvnfk3aEhVS92CpG5eYQHs","blskey_pop":"R5ksJ4cADE265315njhARQJS3eEqC4bBFjz6sW49Lc7cF814pMdKk4AjjHLCvKeViU6EP8YvhvpiuMhEYzmabsMnj7oesJGp6eqxkjSabFZ7LWk5wzfBW53fSAtBrK4KSGzAfU9zsmsNqBMuK1fiC7J1emxjaZfeXWkBV63dD36i95","client_ip":"63.33.233.212","client_port":9702,"node_ip":"34.250.128.221","node_port":9701,"services":["VALIDATOR"]},"dest":"5mYsynpwzx3muLWYP5ZmqWK8oZtP5k7xj5w85NDKJSM6"},"metadata":{"digest":"c4b76246a7000c018f70776f56accb4667a00eaab83933246b218f5cf4234fb2","from":"6aAJEoijPh3yqeDe9JCqRg","reqId":1553027997718160009},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":126,"txnTime":1553027997},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6aAJEoijPh3yqeDe9JCqRg","value":"2KPdokKdhFMouCFG2xFUmPpwhUSUzte5CdjykDd4YE1BdjZ8UuHsW7d7CLiLrcUkLxyFfKKZNHPZaLhTHjoSqTvF"}]},"txn":{"data":{"data":{"alias":"DigiCert-Node","blskey":"34ZnAnof88aahn2jShZqUPyvWXjM4pdjBukbH44BXAyZGcY2rivuMLHwCPZGGRf7JAVx6zGdnQiFziqwj8ndtGghht67H2DqHhoxxeCvgxv5aMAVW57z8ekGyMQXq1UWYEYgjFqY2Fi9bCTkGb4ywn4JEFvnfk3aEhVS92CpG5eYQHs","blskey_pop":"R5ksJ4cADE265315njhARQJS3eEqC4bBFjz6sW49Lc7cF814pMdKk4AjjHLCvKeViU6EP8YvhvpiuMhEYzmabsMnj7oesJGp6eqxkjSabFZ7LWk5wzfBW53fSAtBrK4KSGzAfU9zsmsNqBMuK1fiC7J1emxjaZfeXWkBV63dD36i95","client_ip":"34.250.128.221","client_port":9702,"node_ip":"34.250.128.221","node_port":9701,"services":["VALIDATOR"]},"dest":"5mYsynpwzx3muLWYP5ZmqWK8oZtP5k7xj5w85NDKJSM6"},"metadata":{"digest":"f5597be009afc3b9decda1e09c8f8d74bd039ac6d6cb890051c88ebeffaa16e9","from":"6aAJEoijPh3yqeDe9JCqRg","reqId":1553030138803227716},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":127,"txnTime":1553030138},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"2v7MzY14jwoMvakDWybDyf3o977GWDT1rQMd5eCEnmTjXy5kvoH99zZJVWXJibDYLU137JbsxQkh59T1HD4kNBbS"}]},"txn":{"data":{"data":{"alias":"brazil","services":[]},"dest":"2MHGDD2XpRJohQzsXu4FAANcmdypfNdpcqRbqnhkQsCq"},"metadata":{"digest":"8bca858f90639c68074c2bb5cdec23a1763e3f58ad38d82c8ebcc0c229c72daa","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"a89394bc38773f420ee0fa62a7e4be466a668c996b1a956e173c8c62ad4ec8ff","reqId":1558034106037397876},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":128,"txnTime":1558034106},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"QVvkFcetzE4g22g4px1iHqEonHimSsFBh7XkCTmEeBqK7oaN4j3TCDB7BjTxZmGhovc2GWVxRswN65nvFygYn7E"}]},"txn":{"data":{"data":{"alias":"canada","services":[]},"dest":"8NZ6tbcPN2NVvf2fVhZWqU11XModNudhbe15JSctCXab"},"metadata":{"digest":"59a7f83dc11a5fbb89f21738665dde7818425cade9b27ff2c944879d48aa2890","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"b9597e20b484f700011436a6c8b768918519980f808928de56b24225165e86e1","reqId":1558034149584193019},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":129,"txnTime":1558034149},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"gc57GehP57PUUN59tGadDjdE7LAmrfseFCqb1DgYenmFF73cUAF25gkPJxuLiFpPV5CiFPjSoAa1JQhAGUz5MDk"}]},"txn":{"data":{"data":{"alias":"england","services":[]},"dest":"DNuLANU7f1QvW1esN3Sv9Eap9j14QuLiPeYzf28Nub4W"},"metadata":{"digest":"f59bb98e1be7576c6575a5abc4ac38edd28148d84605a62437aaf3151c962f69","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"fe0b9e5440ac37828d86daa77be8a55c2b52c89274763673db58d9317c2d1925","reqId":1558034190209122296},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":130,"txnTime":1558034190},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"kU4vKskb9iZEy7YfUQWt9cVrYpmXcqTpctKCLnZ4JsQ9DCEL9ESZ9waC8Jw6qFAW7gQfDgqkehmkFbD9jZSgsyf"}]},"txn":{"data":{"data":{"alias":"korea","services":[]},"dest":"HCNuqUoXuK9GXGd2EULPaiMso2pJnxR6fCZpmRYbc7vM"},"metadata":{"digest":"45a6090bbffa535b34b103f0f4209b039e66c05fad50013d30e8cb139510d627","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"5724525adf9d82d113b1f785c39c6a3a6f6ea92e06e9dbdb57740f4e1e8a3654","reqId":1558034231813907676},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":131,"txnTime":1558034232},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"5VB9ELm2QkWug8s29obUiSxq5tWi588wnYX5nXJEmm5ewoVR5NSTHQcg9q2nm1rcy2aesVsB77e5LLWpae7uqjkb"}]},"txn":{"data":{"data":{"alias":"singapore","services":[]},"dest":"Dh99uW8jSNRBiRQ4JEMpGmJYvzmF35E6ibnmAAf7tbk8"},"metadata":{"digest":"0fb35dbf7589f7acfa487aa1eed473299d1405ed2c656b50b15175e5487f4aa9","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"0359d686d0935df89d4b8fbad5b7264104f2eef898a93cc388e67188d71980e4","reqId":1558034282845794593},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":132,"txnTime":1558034283},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"2wMAYGVoSt3ZtU3CzcvhzMAQ2EFhZvPwBsGKqpNmWvKVWbFeyC6yMZxHCVdtj2fqfj36FJkvPKkh3gwmvvrt6eeW"}]},"txn":{"data":{"data":{"alias":"virginia","services":[]},"dest":"EoGRm7eRADtHJRThMCrBXMUM2FpPRML19tNxDAG8YTP8"},"metadata":{"digest":"4ee79b267e7a5aa9ca96ee667f75dadfafdf1238c2841d2d5fa28accf542331c","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"1b4afb24a5d19cf90eb41bb9c265610a6922c6e0aa59386dc3af2016988f4dc6","reqId":1558034311268528084},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":133,"txnTime":1558034311},"ver":"1"} -{"reqSignature":{"type":"ED25519","values":[{"from":"6feBTywcmJUriqqnGc1zSJ","value":"3QyhWLE53fg8wsNRFJijV6QKax2deCAzhUa1g152KYJ7exk1iqv6mLsHtY5KXNiXLg1a8vxQiFj8Hp8iRb7CXrCA"}]},"txn":{"data":{"data":{"alias":"RFCU","services":[]},"dest":"2B8bkZX3SvcBq3amP7aeATsSPz82RyyCJQbEjZpLgZLh"},"metadata":{"digest":"58c132bc0b8c96fb7628e8b7af9a9086ff2b2c894091090f88eb328ee945fe28","from":"6feBTywcmJUriqqnGc1zSJ","payloadDigest":"64b6882e3cbf71154369d91f9da431d069b465116dada6ba685928cd11c7b9e3","reqId":1558034435821210238},"protocolVersion":2,"type":"0"},"txnMetadata":{"seqNo":134,"txnTime":1558034436},"ver":"1"} -' \ No newline at end of file diff --git a/apps/agent-provisioning/AFJ/docker-compose_160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.yaml b/apps/agent-provisioning/AFJ/docker-compose_160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.yaml new file mode 100644 index 000000000..d26d35b74 --- /dev/null +++ b/apps/agent-provisioning/AFJ/docker-compose_160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.yaml @@ -0,0 +1,23 @@ +version: '3' + +services: + agent: + image: + + container_name: 160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_ + restart: always + environment: + AFJ_REST_LOG_LEVEL: 1 + ports: + - 9001:9001 + - 8001:8001 + + volumes: + - ./agent-config/160bacc1-11d6-4f9f-b1bb-a398d63e6c6d_.json:/config.json + + command: --auto-accept-connections --config /config.json + +volumes: + pgdata: + agent-indy_client: + agent-tmp: diff --git a/apps/agent-provisioning/AFJ/docker-compose_4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin.yaml b/apps/agent-provisioning/AFJ/docker-compose_4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin.yaml new file mode 100644 index 000000000..75782113a --- /dev/null +++ b/apps/agent-provisioning/AFJ/docker-compose_4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin.yaml @@ -0,0 +1,23 @@ +version: '3' + +services: + agent: + image: afj-0.4.2:latest + + container_name: 4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin + restart: always + environment: + AFJ_REST_LOG_LEVEL: 1 + ports: + - 9002:9002 + - 8002:8002 + + volumes: + - ./agent-config/4c68c7a3-d251-4765-9669-cfb8d852f9ff_Platform-admin.json:/config.json + + command: --auto-accept-connections --config /config.json + +volumes: + pgdata: + agent-indy_client: + agent-tmp: diff --git a/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt b/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt index e002b3628..c23f2978f 100644 --- a/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt +++ b/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt @@ -1 +1 @@ -8000 +8002 diff --git a/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt b/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt index d58c55a31..ab375f2f0 100644 --- a/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt +++ b/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt @@ -1 +1 @@ -9000 +9002 diff --git a/apps/agent-provisioning/AFJ/scripts/docker_start_agent.sh b/apps/agent-provisioning/AFJ/scripts/docker_start_agent.sh index 1b1269ab2..ab3673453 100644 --- a/apps/agent-provisioning/AFJ/scripts/docker_start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/docker_start_agent.sh @@ -17,13 +17,6 @@ PROTOCOL=${12} TENANT=${13} AFJ_VERSION=${14} INDY_LEDGER=${15} -INBOUND_ENDPOINT=${16} -SCHEMA_FILE_SERVER_URL=${17} -AGENT_API_KEY=${18} -ADMIN_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-admin-port.txt" -INBOUND_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-inbound-port.txt" -ADMIN_PORT=8001 -INBOUND_PORT=9001 echo "AGENCY: $AGENCY" echo "EXTERNAL_IP: $EXTERNAL_IP" @@ -41,6 +34,11 @@ echo "TENANT: $TENANT" echo "AFJ_VERSION: $AFJ_VERSION" echo "INDY_LEDGER: $INDY_LEDGER" +ADMIN_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-admin-port.txt" +INBOUND_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-inbound-port.txt" +ADMIN_PORT=8001 +INBOUND_PORT=9001 + increment_port() { local port="$1" local lower_limit="$2" @@ -93,14 +91,14 @@ if [ -d "${PWD}/agent-provisioning/AFJ/endpoints" ]; then echo "Endpoints directory exists." else echo "Error: Endpoints directory does not exists." - mkdir -p ${PWD}/agent-provisioning/AFJ/endpoints + mkdir ${PWD}/agent-provisioning/AFJ/endpoints fi if [ -d "${PWD}/agent-provisioning/AFJ/agent-config" ]; then echo "Endpoints directory exists." else echo "Error: Endpoints directory does not exists." - mkdir -p ${PWD}/agent-provisioning/AFJ/agent-config + mkdir ${PWD}/agent-provisioning/AFJ/agent-config fi AGENT_ENDPOINT="${PROTOCOL}://${EXTERNAL_IP}:${INBOUND_PORT}" @@ -134,21 +132,19 @@ cat <${CONFIG_FILE} "autoAcceptConnections": true, "autoAcceptCredentials": "contentApproved", "autoAcceptProofs": "contentApproved", - "logLevel": 2, + "logLevel": 5, "inboundTransport": [ { "transport": "$PROTOCOL", - "port": $INBOUND_PORT + "port": "$INBOUND_PORT" } ], "outboundTransport": [ "$PROTOCOL" ], "webhookUrl": "$WEBHOOK_HOST/wh/$AGENCY", - "adminPort": $ADMIN_PORT, - "tenancy": $TENANT, - "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL", - "apiKey": "$AGENT_API_KEY" + "adminPort": "$ADMIN_PORT", + "tenancy": $TENANT } EOF @@ -174,8 +170,6 @@ services: environment: AFJ_REST_LOG_LEVEL: 1 ROOT_PATH: ${ROOT_PATH} - env_file: - - /app/agent.env ports: - ${INBOUND_PORT}:${INBOUND_PORT} - ${ADMIN_PORT}:${ADMIN_PORT} @@ -200,7 +194,7 @@ if [ $? -eq 0 ]; then echo "container-name::::::${CONTAINER_NAME}" echo "file-name::::::$FILE_NAME" - docker-compose -f $FILE_NAME up -d + docker compose -f $FILE_NAME up -d if [ $? -eq 0 ]; then n=0 @@ -229,7 +223,7 @@ if [ $? -eq 0 ]; then container_logs=$(docker logs $(docker ps -q --filter "name=${AGENCY}_${CONTAINER_NAME}")) # Extract the token from the logs using sed - token=$(echo "$container_logs" | sed -nE 's/.*** API Key: ([^ ]+).*/\1/p') + token=$(echo "$container_logs" | sed -nE 's/.*API Toekn: ([^ ]+).*/\1/p') # Print the extracted token echo "Token: $token" diff --git a/apps/agent-provisioning/AFJ/scripts/fargate.sh b/apps/agent-provisioning/AFJ/scripts/fargate.sh index bb8ea9b66..0b0068b08 100644 --- a/apps/agent-provisioning/AFJ/scripts/fargate.sh +++ b/apps/agent-provisioning/AFJ/scripts/fargate.sh @@ -18,17 +18,62 @@ TENANT=${13} AFJ_VERSION=${14} INDY_LEDGER=${15} INBOUND_ENDPOINT=${16} -SCHEMA_FILE_SERVER_URL=${17} -AGENT_API_KEY=${18} -AWS_ACCOUNT_ID=${19} -S3_BUCKET_ARN=${20} -CLUSTER_NAME=${21} -TASKDEFINITION_FAMILY=${22} -ADMIN_TG_ARN=${23} -INBOUND_TG_ARN=${24} -FILESYSTEMID=${25} -ECS_SUBNET_ID=${26} -ECS_SECURITY_GROUP_ID=${27} +AWS_ACCOUNT_ID=${17} +S3_BUCKET_ARN=${18} +CLUSTER_NAME=${19} +FILESYSTEMID=${20} +ACCESSPOINTID=${21} +VPC_ID=${22} +ECS_SUBNET_ID=${23} +ALB_SUBNET_ID_ONE=${24} +ALB_SUBNET_ID_TWO=${25} +EFS_SECURITY_GROUP_ID=${26} +AWS_PUBLIC_REGION=${27} +STAGE=${28} +AGENT_WEBSOCKET_PROTOCOL=${29} +DB_SECURITY_GROUP_ID=${30} +TESKDEFINITION_FAMILY="${STAGE_}_${CONTAINER_NAME}_TASKDEFIITION" + + +echo "START_TIME: $START_TIME" +echo "AGENCY: $AGENCY" +echo "EXTERNAL_IP: $EXTERNAL_IP" +echo "WALLET_NAME: $WALLET_NAME" +echo "WALLET_PASSWORD: $WALLET_PASSWORD" +echo "RANDOM_SEED: $RANDOM_SEED" +echo "WEBHOOK_HOST: $WEBHOOK_HOST" +echo "WALLET_STORAGE_HOST: $WALLET_STORAGE_HOST" +echo "WALLET_STORAGE_PORT: $WALLET_STORAGE_PORT" +echo "WALLET_STORAGE_USER: $WALLET_STORAGE_USER" +echo "WALLET_STORAGE_PASSWORD: $WALLET_STORAGE_PASSWORD" +echo "CONTAINER_NAME: $CONTAINER_NAME" +echo "PROTOCOL: $PROTOCOL" +echo "TENANT: $TENANT" +echo "AFJ_VERSION: $AFJ_VERSION" +echo "INDY_LEDGER: $INDY_LEDGER" +echo "INBOUND_ENDPOINT: $INBOUND_ENDPOINT" +echo "AWS_ACCOUNT_ID: $AWS_ACCOUNT_ID" +echo "S3_BUCKET_ARN: $S3_BUCKET_ARN" +echo "CLUSTER_NAME: $CLUSTER_NAME" +echo "TESKDEFINITION_FAMILY: $TESKDEFINITION_FAMILY" +echo "FILESYSTEMID: $FILESYSTEMID" +echo "ACCESSPOINTID: $ACCESSPOINTID" +echo "VPC_ID: $VPC_ID" +echo "ECS_SUBNET_ID: $ECS_SUBNET_ID" +echo "ALB_SUBNET_ID_ONE: $ALB_SUBNET_ID_ONE" +echo "ALB_SUBNET_ID_TWO: $ALB_SUBNET_ID_TWO" +echo "SSL_CRTS: $SSL_CRTS" +echo "EFS_SECURITY_GROUP_ID: $EFS_SECURITY_GROUP_ID" +echo "AGENT_URL: $AGENT_URL" +echo "AWS_PUBLIC_REGION: $AWS_PUBLIC_REGION" +echo "STAGE: $STAGE" +echo "AGENT_WEBSOCKET_PROTOCOL: $AGENT_WEBSOCKET_PROTOCOL" +echo "ALB_SECURITY_GROUP_ID: $ALB_SECURITY_GROUP_ID" +echo "ADMIN_TG_ARN: $ADMIN_TG_ARN" +echo "INBOUND_TG_ARN: $INBOUND_TG_ARN" +echo "AGENT_INBOUND_URL: $AGENT_INBOUND_URL" +echo "DB_SECURITY_GROUP_ID: $DB_SECURITY_GROUP_ID" + DESIRED_COUNT=1 @@ -42,10 +87,12 @@ random_string=$(generate_random_string) # Print the generated random string echo "Random String: $random_string" -SERVICE_NAME="${CONTAINER_NAME}-service" +SERVICE_NAME="${AGENCY}-${CONTAINER_NAME}-service-${random_string}" EXTERNAL_IP=$(echo "$2" | tr -d '[:space:]') ADMIN_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-admin-port.txt" INBOUND_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-inbound-port.txt" +echo "AGENCY: $SERVICE_NAME" +echo "EXTERNAL_IP: $EXTERNAL_IP" ADMIN_PORT=8001 INBOUND_PORT=9001 @@ -96,24 +143,209 @@ echo "Last used admin port: $ADMIN_PORT" echo "Last used inbound port: $INBOUND_PORT" echo "AGENT SPIN-UP STARTED" -# Define a regular expression pattern for IP address -IP_REGEX="^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$" -# Check if INBOUND_ENDPOINT is a domain or IP address -if [[ $INBOUND_ENDPOINT =~ ^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then - echo "INBOUND_ENDPOINT is a domain: $INBOUND_ENDPOINT" - # Extracting the domain name without the protocol - AGENT_ENDPOINT=$(echo "$INBOUND_ENDPOINT" | sed 's/^https\?:\/\///') -else - # Check if the input is an IP address - if [[ $INBOUND_ENDPOINT =~ $IP_REGEX ]]; then - echo "INBOUND_ENDPOINT is an IP address: $INBOUND_ENDPOINT" - # Adding the protocol to the IP address - AGENT_ENDPOINT="${PROTOCOL}://${INBOUND_ENDPOINT}:${INBOUND_PORT}" - else - echo "Invalid input for INBOUND_ENDPOINT: $INBOUND_ENDPOINT" - fi -fi +#CLUSTER_NAME=$(aws ecs create-cluster --cluster-name ${CONTAINER_NAME}) + +# Create security groups +ALB_SECURITY_GROUP_ID=$(aws ec2 create-security-group --group-name "${STAGE}-${AGENCY}-${random_string}-alb-sg" --description "Security group for ALB" --vpc-id $VPC_ID --output text) +ECS_SECURITY_GROUP_ID=$(aws ec2 create-security-group --group-name "${STAGE}-${AGENCY}-${random_string}-ecs-sg" --description "Security group for ECS Fargate service" --vpc-id $VPC_ID --output text) + +echo "ALB_SECURITY_GROUP_ID:$ALB_SECURITY_GROUP_ID" +echo "ECS_SECURITY_GROUP_ID:$ECS_SECURITY_GROUP_ID" +echo "EFS_SECURITY_GROUP_ID:$SECURITY_GROUP_ID" + +# Allow inbound traffic from the ECS Fargate security group to the EFS security group on NFS port +aws ec2 authorize-security-group-ingress \ + --group-id "$EFS_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port 2049 \ + --source-group "$ECS_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-allow},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize inbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-ingress \ + --group-id "$ECS_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$ADMIN_PORT" \ + --source-group "$ALB_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-alb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + + +# Authorize outbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-egress \ + --group-id "$ECS_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$ADMIN_PORT" \ + --source-group "$ALB_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-alb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + + +# Authorize inbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-ingress \ + --group-id "$ECS_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$INBOUND_PORT" \ + --source-group "$ALB_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-alb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize outbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-egress \ + --group-id "$ECS_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$INBOUND_PORT" \ + --source-group "$ALB_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-alb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize inbound traffic for ECS security group from DB security group +aws ec2 authorize-security-group-ingress \ + --group-id "$DB_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$WALLET_STORAGE_PORT" \ + --source-group "$ECS_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-ecs-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize outbound traffic for ECS security group from DB security group +aws ec2 authorize-security-group-egress \ + --group-id "$DB_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$WALLET_STORAGE_PORT" \ + --source-group "$ECS_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-ecs-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize inbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-ingress \ + --group-id "$ALB_SECURITY_GROUP_ID" \ + --ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0,Description="Allowing 0.0.0.0/0 to the LB port"}]' \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=allow-the-world}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize outbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-egress \ + --group-id "$ALB_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$ADMIN_PORT" \ + --source-group "$ECS_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-adminalb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize inbound traffic for ALB security group from ECS security group +aws ec2 authorize-security-group-ingress \ + --group-id "$ALB_SECURITY_GROUP_ID" \ + --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0,Description="Allowing 0.0.0.0/0 to the LB port"}]' \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=allow-the-world}]" \ + --region $AWS_PUBLIC_REGION + +# Authorize outbound traffic of ALB security group for ECS security group +aws ec2 authorize-security-group-egress \ + --group-id "$ALB_SECURITY_GROUP_ID" \ + --protocol tcp \ + --port "$INBOUND_PORT" \ + --source-group "$ECS_SECURITY_GROUP_ID" \ + --tag-specifications "ResourceType=security-group-rule,Tags=[{Key=Name,Value=${STAGE}-${AGENCY}-${CONTAINER_NAME}-inboundalb-sg},{Key=ENV,Value=test}]" \ + --region $AWS_PUBLIC_REGION + + +# Create Target Groups for admin port +ADMIN_TG_ARN=$(aws elbv2 create-target-group \ + --name "${STAGE}-${ADMIN_PORT}-tg" \ + --protocol HTTP \ + --port 80 \ + --target-type ip \ + --vpc-id $VPC_ID \ + --health-check-protocol HTTP \ + --health-check-port $ADMIN_PORT \ + --health-check-path /agent \ + --health-check-interval-seconds 120 \ + --query 'TargetGroups[0].TargetGroupArn' \ + --output text) + + +echo "admin-tg-arm: $ADMIN_TG_ARN" + +# Create Target Groups for inbound port +INBOUND_TG_ARN=$(aws elbv2 create-target-group --name "${STAGE}-${INBOUND_PORT}-tg" --protocol HTTP --port 80 --target-type ip --vpc-id $VPC_ID --query 'TargetGroups[0].TargetGroupArn' --output text) + +echo "admin-tg-arm: $INBOUND_TG_ARN" + + +# Create Application Load Balancer +ADMIN_ALB_ARN=$(aws elbv2 create-load-balancer \ +--name $STAGE-$CONTAINER_NAME-${ADMIN_PORT}-alb \ +--subnets $ALB_SUBNET_ID_ONE $ALB_SUBNET_ID_TWO \ +--tags "[{\"Key\":\"Name\", \"Value\":\"${CONTAINER_NAME}-alb\"}]" \ +--type application \ +--scheme internet-facing \ +--security-groups $ALB_SECURITY_GROUP_ID \ +--region $AWS_PUBLIC_REGION \ +--query "LoadBalancers[0].LoadBalancerArn" \ +--output text) + +# Describe the ALB to retrieve its DNS name +ADMIN_ALB_DNS=$(aws elbv2 describe-load-balancers \ +--load-balancer-arns $ADMIN_ALB_ARN \ +--query "LoadBalancers[0].DNSName" \ +--output text) + +echo "ALB DNS: $ADMIN_ALB_DNS" + +# Create HTTP listener +aws elbv2 create-listener \ + --load-balancer-arn "$ADMIN_ALB_ARN" \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=forward,TargetGroupArn="$ADMIN_TG_ARN" \ + --region "$AWS_PUBLIC_REGION" + + + +# Create Application Load Balancer +INBOUND_ALB_ARN=$(aws elbv2 create-load-balancer \ +--name $STAGE-$CONTAINER_NAME-${INBOUND_PORT}-alb \ +--subnets $ALB_SUBNET_ID_ONE $ALB_SUBNET_ID_TWO \ +--tags "[{\"Key\":\"Name\", \"Value\":\"${CONTAINER_NAME}-alb\"}]" \ +--type application \ +--scheme internet-facing \ +--security-groups $ALB_SECURITY_GROUP_ID \ +--region $AWS_PUBLIC_REGION \ +--query "LoadBalancers[0].LoadBalancerArn" \ +--output text) + +# Describe the ALB to retrieve its DNS name +INBOUND_ALB_DNS=$(aws elbv2 describe-load-balancers \ +--load-balancer-arns $INBOUND_ALB_ARN \ +--query "LoadBalancers[0].DNSName" \ +--output text) + +echo "INBOUND_ALB DNS: $INBOUND_ALB_DNS" + +#add listner to inbound +aws elbv2 create-listener \ + --load-balancer-arn $INBOUND_ALB_ARN \ + --protocol HTTP \ + --port 80 \ + --default-actions Type=forward,TargetGroupArn=$INBOUND_TG_ARN \ + --region $AWS_PUBLIC_REGION + + +# modify health check of inboud tg +aws elbv2 modify-target-group \ + --target-group-arn $INBOUND_TG_ARN \ + --health-check-protocol HTTP \ + --health-check-port "traffic-port" \ + --health-check-path "/" \ + --health-check-interval-seconds 30 \ + --healthy-threshold-count 3 \ + --unhealthy-threshold-count 3 \ + --matcher "HttpCode=404" \ + --region $AWS_PUBLIC_REGION + # Generate the agent config JSON cat <$PWD/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json @@ -130,7 +362,7 @@ cat <$PWD/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}. "walletScheme": "DatabasePerWallet", "indyLedger": $INDY_LEDGER, "endpoint": [ - "$INBOUND_ENDPOINT" + "http://$INBOUND_ALB_DNS" ], "autoAcceptConnections": true, "autoAcceptCredentials": "contentApproved", @@ -138,18 +370,16 @@ cat <$PWD/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}. "logLevel": 5, "inboundTransport": [ { - "transport": "$PROTOCOL", - "port": $INBOUND_PORT + "transport": "$AGENT_WEBSOCKET_PROTOCOL", + "port": "$INBOUND_PORT" } ], "outboundTransport": [ - "$PROTOCOL" + "$AGENT_WEBSOCKET_PROTOCOL" ], "webhookUrl": "$WEBHOOK_HOST/wh/$AGENCY", "adminPort": $ADMIN_PORT, - "tenancy": $TENANT, - "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL", - "apiKey": "$AGENT_API_KEY" + "tenancy": $TENANT } EOF @@ -159,9 +389,9 @@ CONTAINER_DEFINITIONS=$( [ { "name": "$CONTAINER_NAME", - "image": "${AFJ_VERSION}", - "cpu": 307, - "memory": 358, + "image": "${AFJ_IMAGE_URL}", + "cpu": 256, + "memory": 512, "portMappings": [ { "containerPort": $ADMIN_PORT, @@ -194,7 +424,7 @@ CONTAINER_DEFINITIONS=$( ], "mountPoints": [ { - "sourceVolume": "config", + "sourceVolume": "AGENT-CONFIG", "containerPath": "/config", "readOnly": true } @@ -203,16 +433,16 @@ CONTAINER_DEFINITIONS=$( "volumesFrom": [], "ulimits": [], "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-create-group": "true", - "awslogs-group": "/ecs/$TASKDEFINITION_FAMILY", - "awslogs-region": "$AWS_PUBLIC_REGION", - "awslogs-stream-prefix": "ecs" -} - }, - "ulimits": [] -} + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "true", + "awslogs-group": "/ecs/$TESKDEFINITION_FAMILY/$CONTAINER_NAME", + "awslogs-region": "$AWS_PUBLIC_REGION", + "awslogs-stream-prefix": "ecs" + }, + "secretOptions": [] + } + } ] EOF ) @@ -220,27 +450,28 @@ EOF # Define the task definition JSON TASK_DEFINITION=$(cat < task_definition.json TASK_DEFINITION_ARN=$(aws ecs register-task-definition --cli-input-json file://task_definition.json --query 'taskDefinition.taskDefinitionArn' --output text) -SERVICE_JSON=$(cat < service.json +echo "$SERVICE" > service.json # Check if the service file was created successfully -if [ -f "service.json" ]; then - echo "Service file created successfully: service.json" +if [ -f "$SERVICE_FILE" ]; then + echo "Service file created successfully: $SERVICE_FILE" else - echo "Failed to create service file: service.json" + echo "Failed to create service file: $SERVICE_FILE" fi # Create the service aws ecs create-service \ - --cli-input-json file://service.json \ - --region $AWS_PUBLIC_REGION +--service-name $SERVICE_NAME \ +--cli-input-json file://service.json \ +--region $AWS_PUBLIC_REGION # Describe the ECS service and filter by service name service_description=$(aws ecs describe-services --service $SERVICE_NAME --cluster $CLUSTER_NAME --region $AWS_PUBLIC_REGION) @@ -312,28 +551,26 @@ else exit 1 fi -if [ $? -eq 0 ]; then - - n=0 - until [ "$n" -ge 6 ]; do - if netstat -tln | grep ${ADMIN_PORT} >/dev/null; then - - AGENTURL="http://${EXTERNAL_IP}:${ADMIN_PORT}/agent" - agentResponse=$(curl -s -o /dev/null -w "%{http_code}" $AGENTURL) - - if [ "$agentResponse" = "200" ]; then - echo "Agent is running" && break - else +# Wait for the agent to become ready +# You may need to adjust the number of attempts and sleep time according to your requirements +n=0 +max_attempts=15 +sleep_time=10 +AGENT_HEALTHCHECK_URL="http://$ADMIN_ALB_DNS/agent" +echo "--------AGENT_HEALTHCHECK_URL-----$AGENT_URL" +until [ "$n" -ge "$max_attempts" ]; do + agentResponse=$(curl -s -o /dev/null -w "%{http_code}" "$AGENT_HEALTHCHECK_URL") + if [ "$agentResponse" = "200" ]; then + echo "Agent is running" + break + else echo "Agent is not running" n=$((n + 1)) - sleep 10 - fi - else - echo "No response from agent" - n=$((n + 1)) - sleep 10 + sleep "$sleep_time" fi - done +done + + # Describe the ECS service and filter by service name service_description=$(aws ecs describe-services --service $SERVICE_NAME --cluster $CLUSTER_NAME --region $AWS_PUBLIC_REGION) @@ -341,22 +578,19 @@ echo "service_description=$service_description" # Extract Task ID from the service description events -task_id=$(echo "$service_description" | jq -r ' - .services[0].events[] - | select(.message | test("has started 1 tasks")) - | .message - | capture("\\(task (?[^)]+)\\)") - | .id -') - -# to fetch log group of container -log_group=/ecs/$TASKDEFINITION_FAMILY -echo "log_group=$log_group" +task_id=$(echo "$service_description" | jq -r '.services[0].events[] | select(.message | test("has started 1 tasks")) | .message | capture("\\(task (?[^)]+)\\)") | .id') +#echo "task_id=$task_id" + +log_group=/ecs/$TESKDEFINITION_FAMILY/$CONTAINER_NAME +#echo "log_group=$log_group" # Get Log Stream Name log_stream=ecs/$CONTAINER_NAME/$task_id -echo "logstrem=$log_stream" +#echo "logstrem=$log_stream" + +# Fetch logs +#echo "$(aws logs get-log-events --log-group-name "/ecs/$TESKDEFINITION_FAMILY/$CONTAINER_NAME" --log-stream-name "$log_stream" --region $AWS_PUBLIC_REGION)" # Check if the token folder exists, and create it if it doesn't token_folder="$PWD/agent-provisioning/AFJ/token" @@ -367,6 +601,7 @@ fi # Set maximum retry attempts RETRIES=3 +# Loop to attempt retrieving token from logs # Loop to attempt retrieving token from logs for attempt in $(seq 1 $RETRIES); do echo "Attempt $attempt: Checking service logs for token..." @@ -375,13 +610,9 @@ for attempt in $(seq 1 $RETRIES); do token=$(aws logs get-log-events \ --log-group-name "$log_group" \ --log-stream-name "$log_stream" \ - --region $AWS_PUBLIC_REGION \ - --query 'events[*].message' \ - --output text \ - | tr -d '\033' \ - | grep 'API Key:' \ - | sed -E 's/.*API Key:[[:space:]]*([a-zA-Z0-9._:-]*).*/\1/' \ - | head -n 1 + --region ap-southeast-1 \ + | grep -o 'API Token: [^ ]*' \ + | cut -d ' ' -f 3 ) # echo "token=$token" if [ -n "$token" ]; then @@ -399,23 +630,44 @@ for attempt in $(seq 1 $RETRIES); do sleep 10 done - echo "Creating agent config" - cat <${PWD}/agent-provisioning/AFJ/endpoints/${AGENCY}_${CONTAINER_NAME}.json - { - "CONTROLLER_ENDPOINT":"$EXTERNAL_IP" - } -EOF - cat <${PWD}/agent-provisioning/AFJ/token/${AGENCY}_${CONTAINER_NAME}.json - { - "token" : "$token" - } +# Print variable values for debugging +echo "AGENCY: $AGENCY" +echo "CONTAINER_NAME: $CONTAINER_NAME" +echo "AGENT_URL: $AGENT_URL" +echo "AGENT_INBOUND_URL: $AGENT_INBOUND_URL" + +## Construct file path for agent config +config_file="${PWD}/agent-provisioning/AFJ/endpoints/${AGENCY}_${CONTAINER_NAME}.json" + +# Check if the directory exists and create it if it doesn't +config_dir=$(dirname "$config_file") +if [ ! -d "$config_dir" ]; then + mkdir -p "$config_dir" +fi + +# Create agent config +echo "Creating agent config" +cat <"$config_file" +{ + "CONTROLLER_ENDPOINT": "$ADMIN_ALB_DNS", + "AGENT_ENDPOINT": "$INBOUND_ALB_DNS" +} EOF - echo "Agent config created" +# Check if the file was created successfully +if [ -f "$config_file" ]; then + echo "Agent config created successfully: $config_file" else - echo "===============" - echo "ERROR : Failed to spin up the agent!" - echo "===============" && exit 125 + echo "Failed to create agent config: $config_file" fi -echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)" \ No newline at end of file + +# Print available folders in the AFJ directory +echo "Available folders in the AFJ directory:" +ls -d "${PWD}/agent-provisioning/AFJ/"*/ + +# Print the content of the JSON files +echo "Content of endpoint JSON file:" +cat "$config_file" +echo "Content of token JSON file:" +cat "${PWD}/agent-provisioning/AFJ/token/${AGENCY}_${CONTAINER_NAME}.json" \ No newline at end of file diff --git a/apps/agent-provisioning/AFJ/scripts/on_premises_agent.sh b/apps/agent-provisioning/AFJ/scripts/on_premises_agent.sh index 5b1136290..54084f000 100644 --- a/apps/agent-provisioning/AFJ/scripts/on_premises_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/on_premises_agent.sh @@ -148,14 +148,7 @@ prompt_input "Enter WALLET_STORAGE_PORT: " WALLET_STORAGE_PORT prompt_input "Enter WALLET_STORAGE_USER: " WALLET_STORAGE_USER prompt_input "Enter WALLET_STORAGE_PASSWORD: " WALLET_STORAGE_PASSWORD prompt_input "Enter AGENT_NAME: " AGENT_NAME -pronections": true, - "autoAcceptCredentials": "contentApproved", - "autoAcceptProofs": "contentApproved", - "logLevel": 2, - "inboundTransport": [ - { - "transport": "$PROTOCOL", - "port": $INBmpt_input "Enter PROTOCOL: " PROTOCOL +prompt_input "Enter PROTOCOL: " PROTOCOL prompt_input_with_tenant_validation "Enter TENANT (true/false): " TENANT "Error: TENANT must be either 'true' or 'false'." prompt_input "Enter CREDO_IMAGE: " CREDO_IMAGE prompt_input "Enter INBOUND_ENDPOINT: " INBOUND_ENDPOINT @@ -345,4 +338,4 @@ else echo "ERROR : Failed to execute!" && exit 125 fi -echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)" \ No newline at end of file +echo "Total time elapsed: $(date -ud "@$(($(date +%s) - $START_TIME))" +%T) (HH:MM:SS)" diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent.sh b/apps/agent-provisioning/AFJ/scripts/start_agent.sh index 715a9baa6..311c41b4c 100755 --- a/apps/agent-provisioning/AFJ/scripts/start_agent.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent.sh @@ -19,7 +19,7 @@ AFJ_VERSION=${14} INDY_LEDGER=${15} INBOUND_ENDPOINT=${16} SCHEMA_FILE_SERVER_URL=${17} -AGENT_API_KEY=${18} + ADMIN_PORT_FILE="$PWD/apps/agent-provisioning/AFJ/port-file/last-admin-port.txt" INBOUND_PORT_FILE="$PWD/apps/agent-provisioning/AFJ/port-file/last-inbound-port.txt" ADMIN_PORT=8001 @@ -122,7 +122,7 @@ if [ -f "$CONFIG_FILE" ]; then rm "$CONFIG_FILE" fi -cat <"$CONFIG_FILE" +cat <${CONFIG_FILE} { "label": "${AGENCY}_${CONTAINER_NAME}", "walletId": "$WALLET_NAME", @@ -141,7 +141,7 @@ cat <"$CONFIG_FILE" "autoAcceptConnections": true, "autoAcceptCredentials": "contentApproved", "autoAcceptProofs": "contentApproved", - "logLevel": 2, + "logLevel": 5, "inboundTransport": [ { "transport": "$PROTOCOL", @@ -154,8 +154,7 @@ cat <"$CONFIG_FILE" "webhookUrl": "$WEBHOOK_HOST/wh/$AGENCY", "adminPort": $ADMIN_PORT, "tenancy": $TENANT, - "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL", - "apiKey": "$AGENT_API_KEY" + "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL" } EOF @@ -182,8 +181,6 @@ services: ports: - ${INBOUND_PORT}:${INBOUND_PORT} - ${ADMIN_PORT}:${ADMIN_PORT} - env_file: - - ../../../agent.env volumes: - ./agent-config/${AGENCY}_${CONTAINER_NAME}.json:/config.json @@ -239,7 +236,7 @@ if [ $? -eq 0 ]; then container_logs=$(docker logs $(docker ps -q --filter "name=${AGENCY}_${CONTAINER_NAME}")) # Extract the token from the logs using sed - token=$(echo "$container_logs" | sed -nE 's/.*** API Key: ([^ ]+).*/\1/p') + token=$(echo "$container_logs" | sed -nE 's/.*API Token: ([^ ]+).*/\1/p') # Print the extracted token echo "Token: $token" diff --git a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh index 3bedb6f36..7bb9a285c 100644 --- a/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh +++ b/apps/agent-provisioning/AFJ/scripts/start_agent_ecs.sh @@ -19,14 +19,11 @@ AFJ_VERSION=${14} INDY_LEDGER=${15} INBOUND_ENDPOINT=${16} SCHEMA_FILE_SERVER_URL=${17} -AGENT_API_KEY=${18} +AGENT_HOST=${18} AWS_ACCOUNT_ID=${19} S3_BUCKET_ARN=${20} CLUSTER_NAME=${21} -TASKDEFINITION_FAMILY=${22} -ADMIN_TG_ARN=${23} -INBOUND_TG_ARN=${24} -FILESYSTEMID=${25} +TESKDEFINITION_FAMILY=${22} DESIRED_COUNT=1 @@ -40,7 +37,7 @@ random_string=$(generate_random_string) # Print the generated random string echo "Random String: $random_string" -SERVICE_NAME="${CONTAINER_NAME}-service" +SERVICE_NAME="${AGENCY}-${CONTAINER_NAME}-service-${random_string}" EXTERNAL_IP=$(echo "$2" | tr -d '[:space:]') ADMIN_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-admin-port.txt" INBOUND_PORT_FILE="$PWD/agent-provisioning/AFJ/port-file/last-inbound-port.txt" @@ -127,16 +124,16 @@ cat </app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}. "walletScheme": "DatabasePerWallet", "indyLedger": $INDY_LEDGER, "endpoint": [ - "$INBOUND_ENDPOINT" + "$AGENT_ENDPOINT" ], "autoAcceptConnections": true, "autoAcceptCredentials": "contentApproved", "autoAcceptProofs": "contentApproved", - "logLevel": 2, + "logLevel": 5, "inboundTransport": [ { "transport": "$PROTOCOL", - "port": $INBOUND_PORT + "port": "$INBOUND_PORT" } ], "outboundTransport": [ @@ -145,10 +142,10 @@ cat </app/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}. "webhookUrl": "$WEBHOOK_HOST/wh/$AGENCY", "adminPort": $ADMIN_PORT, "tenancy": $TENANT, - "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL", - "apiKey": "$AGENT_API_KEY" + "schemaFileServerURL": "$SCHEMA_FILE_SERVER_URL" } EOF +# scp ${PWD}/agent-provisioning/AFJ/agent-config/${AGENCY}_${CONTAINER_NAME}.json ${AGENT_HOST}:/home/ec2-user/config/ # Construct the container definitions dynamically CONTAINER_DEFINITIONS=$( @@ -156,9 +153,9 @@ CONTAINER_DEFINITIONS=$( [ { "name": "$CONTAINER_NAME", - "image": "${AFJ_VERSION}", - "cpu": 307, - "memory": 358, + "image": "${AFJ_IMAGE_URL}", + "cpu": 154, + "memory": 307, "portMappings": [ { "containerPort": $ADMIN_PORT, @@ -175,7 +172,7 @@ CONTAINER_DEFINITIONS=$( "command": [ "--auto-accept-connections", "--config", - "/config/${AGENCY}_${CONTAINER_NAME}.json" + "/config.json" ], "environment": [ { @@ -192,22 +189,13 @@ CONTAINER_DEFINITIONS=$( "mountPoints": [ { "sourceVolume": "config", - "containerPath": "/config", + "containerPath": "/config.json", "readOnly": true } ], "volumesFrom": [], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "/ecs/$TASKDEFINITION_FAMILY", - "awslogs-create-group": "true", - "awslogs-region": "$AWS_PUBLIC_REGION", - "awslogs-stream-prefix": "ecs" - } - }, - "ulimits": [] -} + "ulimits": [] + } ] EOF ) @@ -216,27 +204,23 @@ EOF TASK_DEFINITION=$( cat <task_definition.json # Register the task definition and retrieve the ARN TASK_DEFINITION_ARN=$(aws ecs register-task-definition --cli-input-json file://task_definition.json --query 'taskDefinition.taskDefinitionArn' --output text) -SERVICE_JSON=$( - cat < service.json - -# Check if the service file was created successfully -if [ -f "service.json" ]; then - echo "Service file created successfully: service.json" -else - echo "Failed to create service file: service.json" -fi - # Create the service aws ecs create-service \ - --cli-input-json file://service.json \ - --region $AWS_PUBLIC_REGION - -# Describe the ECS service and filter by service name -service_description=$(aws ecs describe-services --service $SERVICE_NAME --cluster $CLUSTER_NAME --region $AWS_PUBLIC_REGION) - -# Check if the service creation was successful -if [ $? -eq 0 ]; then - echo "Service creation successful" -else - echo "Failed to create service" - exit 1 -fi + --cluster $CLUSTER_NAME \ + --service-name $SERVICE_NAME \ + --task-definition $TASK_DEFINITION_ARN \ + --desired-count $DESIRED_COUNT \ + --launch-type EC2 \ + --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100" if [ $? -eq 0 ]; then @@ -321,81 +263,17 @@ if [ $? -eq 0 ]; then fi done -# Describe the ECS service and filter by service name -service_description=$(aws ecs describe-services --service $SERVICE_NAME --cluster $CLUSTER_NAME --region $AWS_PUBLIC_REGION) -echo "service_description=$service_description" - - -# Extract Task ID from the service description events -task_id=$(echo "$service_description" | jq -r ' - .services[0].events[] - | select(.message | test("has started 1 tasks")) - | .message - | capture("\\(task (?[^)]+)\\)") - | .id -') - -# to fetch log group of container -log_group=/ecs/$TASKDEFINITION_FAMILY -echo "log_group=$log_group" - -# Get Log Stream Name -log_stream=ecs/$CONTAINER_NAME/$task_id - -echo "logstrem=$log_stream" - -# Check if the token folder exists, and create it if it doesn't -token_folder="$PWD/agent-provisioning/AFJ/token" -if [ ! -d "$token_folder" ]; then - mkdir -p "$token_folder" -fi - -# Set maximum retry attempts -RETRIES=3 - -# Loop to attempt retrieving token from logs -for attempt in $(seq 1 $RETRIES); do - echo "Attempt $attempt: Checking service logs for token..." - - # Fetch logs and grep for API token - token=$(aws logs get-log-events \ - --log-group-name "$log_group" \ - --log-stream-name "$log_stream" \ - --region $AWS_PUBLIC_REGION \ - --query 'events[*].message' \ - --output text \ - | tr -d '\033' \ - | grep 'API Key:' \ - | sed -E 's/.*API Key:[[:space:]]*([a-zA-Z0-9._:-]*).*/\1/' \ - | head -n 1 -) - # echo "token=$token" - if [ -n "$token" ]; then - echo "Token found: $token" - # Write token to a file - echo "{\"token\": \"$token\"}" > "$PWD/agent-provisioning/AFJ/token/${AGENCY}_${CONTAINER_NAME}.json" - break # Exit loop if token is found - else - echo "Token not found in logs. Retrying..." - if [ $attempt -eq $RETRIES ]; then - echo "Reached maximum retry attempts. Token not found." - fi - fi - # Add a delay of 10 seconds between retries - sleep 10 -done - - echo "Creating agent config" cat <${PWD}/agent-provisioning/AFJ/endpoints/${AGENCY}_${CONTAINER_NAME}.json { - "CONTROLLER_ENDPOINT":"$EXTERNAL_IP" + "CONTROLLER_ENDPOINT":"${EXTERNAL_IP}:${ADMIN_PORT}", + "AGENT_ENDPOINT" : "${INTERNAL_IP}:${ADMIN_PORT}" } EOF cat <${PWD}/agent-provisioning/AFJ/token/${AGENCY}_${CONTAINER_NAME}.json { - "token" : "$token" + "token" : "" } EOF diff --git a/apps/agent-provisioning/src/agent-provisioning.module.ts b/apps/agent-provisioning/src/agent-provisioning.module.ts index 829667fc9..c088be39f 100644 --- a/apps/agent-provisioning/src/agent-provisioning.module.ts +++ b/apps/agent-provisioning/src/agent-provisioning.module.ts @@ -4,34 +4,20 @@ import { AgentProvisioningService } from './agent-provisioning.service'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { CommonConstants, MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; +import { CommonConstants } from '@credebl/common/common.constant'; @Module({ imports: [ ConfigModule.forRoot(), - GlobalConfigModule, - LoggerModule, - PlatformConfig, - ContextInterceptorModule, ClientsModule.register([ { name: 'NATS_CLIENT', transport: Transport.NATS, options: getNatsOptions(CommonConstants.AGENT_PROVISIONING, process.env.AGENT_PROVISIONING_NKEY_SEED) + } ]) ], controllers: [AgentProvisioningController], - providers: [ - AgentProvisioningService, - Logger, - { - provide: MICRO_SERVICE_NAME, - useValue: 'Agent-provisioning' - } - ] + providers: [AgentProvisioningService, Logger] }) -export class AgentProvisioningModule {} +export class AgentProvisioningModule { } diff --git a/apps/agent-provisioning/src/agent-provisioning.service.ts b/apps/agent-provisioning/src/agent-provisioning.service.ts index 2f2cc5b2e..92d382358 100644 --- a/apps/agent-provisioning/src/agent-provisioning.service.ts +++ b/apps/agent-provisioning/src/agent-provisioning.service.ts @@ -9,38 +9,25 @@ dotenv.config(); @Injectable() export class AgentProvisioningService { - constructor(private readonly logger: Logger) {} + + constructor( + private readonly logger: Logger + ) { } /** * Description: Wallet provision - * @param payload + * @param payload * @returns Get DID and verkey */ async walletProvision(payload: IWalletProvision): Promise { try { - const { - containerName, - externalIp, - orgId, - seed, - walletName, - walletPassword, - walletStorageHost, - walletStoragePassword, - walletStoragePort, - walletStorageUser, - webhookEndpoint, - agentType, - protocol, - credoImage, - tenant, - indyLedger, - inboundEndpoint - } = payload; + + const { containerName, externalIp, orgId, seed, walletName, walletPassword, walletStorageHost, walletStoragePassword, walletStoragePort, walletStorageUser, webhookEndpoint, agentType, protocol, credoImage, tenant, indyLedger, inboundEndpoint } = payload; if (agentType === AgentType.AFJ) { // The wallet provision command is used to invoke a shell script - const walletProvision = `${process.cwd() + process.env.AFJ_AGENT_SPIN_UP} ${orgId} "${externalIp}" "${walletName}" "${walletPassword}" ${seed} ${webhookEndpoint} ${walletStorageHost} ${walletStoragePort} ${walletStorageUser} ${walletStoragePassword} ${containerName} ${protocol} ${tenant} ${credoImage} "${indyLedger}" ${inboundEndpoint} ${process.env.SCHEMA_FILE_SERVER_URL} ${process.env.AGENT_API_KEY} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN} ${process.env.CLUSTER_NAME} ${process.env.TASKDEFINITION_FAMILY} ${process.env.ADMIN_TG_ARN} ${process.env.INBOUND_TG_ARN} ${process.env.FILESYSTEMID} ${process.env.ECS_SUBNET_ID} ${process.env.ECS_SECURITY_GROUP_ID}`; + const walletProvision = `${process.cwd() + process.env.AFJ_AGENT_SPIN_UP} ${orgId} "${externalIp}" "${walletName}" "${walletPassword}" ${seed} ${webhookEndpoint} ${walletStorageHost} ${walletStoragePort} ${walletStorageUser} ${walletStoragePassword} ${containerName} ${protocol} ${tenant} ${credoImage} "${indyLedger}" ${inboundEndpoint} ${process.env.SCHEMA_FILE_SERVER_URL} ${process.env.AGENT_HOST} ${process.env.AWS_ACCOUNT_ID} ${process.env.S3_BUCKET_ARN} ${process.env.CLUSTER_NAME} ${process.env.TESKDEFINITION_FAMILY}`; const spinUpResponse: object = new Promise(async (resolve) => { + await exec(walletProvision, async (err, stdout, stderr) => { this.logger.log(`shell script output: ${stdout}`); if (stderr) { diff --git a/apps/agent-provisioning/src/main.ts b/apps/agent-provisioning/src/main.ts index 4e9a61f80..4002bc54c 100644 --- a/apps/agent-provisioning/src/main.ts +++ b/apps/agent-provisioning/src/main.ts @@ -5,7 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { AgentProvisioningModule } from './agent-provisioning.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); async function bootstrap(): Promise { @@ -14,7 +13,6 @@ async function bootstrap(): Promise { transport: Transport.NATS, options: getNatsOptions(CommonConstants.AGENT_PROVISIONING, process.env.AGENT_PROVISIONING_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/agent-service/src/agent-service.controller.ts b/apps/agent-service/src/agent-service.controller.ts index d368781cd..3bc393e17 100644 --- a/apps/agent-service/src/agent-service.controller.ts +++ b/apps/agent-service/src/agent-service.controller.ts @@ -26,7 +26,7 @@ import { import { user } from '@prisma/client'; import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; import { AgentSpinUpStatus } from '@credebl/enum/enum'; -import { SignDataDto } from '../../api-gateway/src/agent-service/dto/agent-service.dto'; +import { SignDataDto, VerifySignatureDto } from 'apps/api-gateway/src/agent-service/dto/agent-service.dto'; @Controller() export class AgentServiceController { @@ -38,7 +38,7 @@ export class AgentServiceController { * @returns Get agent status */ @MessagePattern({ cmd: 'agent-spinup' }) - async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto; user: IUserRequestInterface }): Promise<{ + async walletProvision(payload: { agentSpinupDto: IAgentSpinupDto, user: IUserRequestInterface }): Promise<{ agentSpinupStatus: AgentSpinUpStatus; }> { return this.agentServiceService.walletProvision(payload.agentSpinupDto, payload.user); @@ -47,8 +47,8 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'create-tenant' }) async createTenant(payload: { - createTenantDto: ITenantDto; - user: IUserRequestInterface; + createTenantDto: ITenantDto, + user: IUserRequestInterface, }): Promise { return this.agentServiceService.createTenant(payload.createTenantDto, payload.user); } @@ -57,12 +57,12 @@ export class AgentServiceController { * @returns did */ @MessagePattern({ cmd: 'create-did' }) - async createDid(payload: { createDidDto: IDidCreate; orgId: string; user: IUserRequestInterface }): Promise { + async createDid(payload: { createDidDto: IDidCreate, orgId: string, user: IUserRequestInterface }): Promise { return this.agentServiceService.createDid(payload.createDidDto, payload.orgId, payload.user); } @MessagePattern({ cmd: 'create-wallet' }) - async createWallet(payload: { createWalletDto: IWallet; user: IUserRequestInterface }): Promise { + async createWallet(payload: { createWalletDto: IWallet, user: IUserRequestInterface }): Promise { return this.agentServiceService.createWallet(payload.createWalletDto); } @@ -74,7 +74,7 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-create-w3c-schema' }) - async createW3CSchema(payload: { url; orgId; schemaRequestPayload }): Promise { + async createW3CSchema(payload: { url, orgId, schemaRequestPayload }): Promise { return this.agentServiceService.createW3CSchema(payload.url, payload.orgId, payload.schemaRequestPayload); } @@ -100,8 +100,8 @@ export class AgentServiceController { @MessagePattern({ cmd: 'agent-create-connection-legacy-invitation' }) async createLegacyConnectionInvitation(payload: { connectionPayload: IConnectionDetails; - url: string; - orgId: string; + url: string, + orgId: string, }): Promise { return this.agentServiceService.createLegacyConnectionInvitation( payload.connectionPayload, @@ -113,58 +113,58 @@ export class AgentServiceController { @MessagePattern({ cmd: 'agent-send-credential-create-offer' }) async sendCredentialCreateOffer(payload: { issueData: IIssuanceCreateOffer; - url: string; - orgId: string; + url: string, + orgId: string, }): Promise { return this.agentServiceService.sendCredentialCreateOffer(payload.issueData, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-all-issued-credentials' }) - async getIssueCredentials(payload: { url: string; apiKey: string }): Promise { + async getIssueCredentials(payload: { url: string, apiKey: string }): Promise { return this.agentServiceService.getIssueCredentials(payload.url, payload.apiKey); } //DONE @MessagePattern({ cmd: 'agent-get-issued-credentials-by-credentialDefinitionId' }) - async getIssueCredentialsbyCredentialRecordId(payload: { url: string; orgId: string }): Promise { + async getIssueCredentialsbyCredentialRecordId(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getIssueCredentialsbyCredentialRecordId(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-proof-presentations' }) - async getProofPresentations(payload: { url: string; apiKey: string }): Promise { + async getProofPresentations(payload: { url: string, apiKey: string }): Promise { return this.agentServiceService.getProofPresentations(payload.url, payload.apiKey); } //DONE @MessagePattern({ cmd: 'agent-get-proof-presentation-by-id' }) - async getProofPresentationById(payload: { url: string; orgId: string }): Promise { + async getProofPresentationById(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getProofPresentationById(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-send-proof-request' }) async sendProofRequest(payload: { - proofRequestPayload: ISendProofRequestPayload; - url: string; - orgId: string; + proofRequestPayload: ISendProofRequestPayload, + url: string, + orgId: string, }): Promise { return this.agentServiceService.sendProofRequest(payload.proofRequestPayload, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-verify-presentation' }) - async verifyPresentation(payload: { url: string; orgId: string }): Promise { + async verifyPresentation(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.verifyPresentation(payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'agent-get-all-connections' }) - async getConnections(payload: { url: string; orgId: string }): Promise { + async getConnections(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getConnections(payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-get-connection-details-by-connectionId' }) - async getConnectionsByconnectionId(payload: { url: string; orgId: string }): Promise { + async getConnectionsByconnectionId(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getConnectionsByconnectionId(payload.url, payload.orgId); } @@ -174,30 +174,10 @@ export class AgentServiceController { * @returns Get agent health */ @MessagePattern({ cmd: 'agent-health' }) - async getAgentHealth(payload: { user: user; orgId: string }): Promise { + async getAgentHealth(payload: { user: user, orgId: string }): Promise { return this.agentServiceService.getAgentHealthDetails(payload.orgId); } - /** - * Sign data from agent - * @param payload - * @returns Signed data by agent - */ - @MessagePattern({ cmd: 'sign-data-from-agent' }) - async signData(payload: { data: SignDataDto; orgId: string }): Promise { - return this.agentServiceService.signDataFromAgent(payload.data, payload.orgId); - } - - /** - * Get agent health - * @param payload - * @returns Get agent health - */ - @MessagePattern({ cmd: 'verify-signature-from-agent' }) - async verifysignature(payload: { data: unknown; orgId: string }): Promise { - return this.agentServiceService.verifysignature(payload.data, payload.orgId); - } - @MessagePattern({ cmd: 'get-ledger-config' }) async getLedgerConfig(payload: { user: IUserRequestInterface }): Promise { return this.agentServiceService.getLedgerConfigDetails(payload.user); @@ -206,24 +186,24 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-send-out-of-band-proof-request' }) async sendOutOfBandProofRequest(payload: { - proofRequestPayload: ISendProofRequestPayload; - url: string; - orgId: string; + proofRequestPayload: ISendProofRequestPayload, + url: string, + orgId: string, }): Promise { return this.agentServiceService.sendOutOfBandProofRequest(payload.proofRequestPayload, payload.url, payload.orgId); } //DONE @MessagePattern({ cmd: 'get-agent-verified-proof-details' }) - async getVerifiedProofDetails(payload: { url: string; orgId: string }): Promise { + async getVerifiedProofDetails(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getVerifiedProofDetails(payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-schema-endorsement-request' }) async schemaEndorsementRequest(payload: { - url: string; - orgId: string; - requestSchemaPayload: object; + url: string, + orgId: string, + requestSchemaPayload: object, }): Promise { return this.agentServiceService.schemaEndorsementRequest(payload.url, payload.orgId, payload.requestSchemaPayload); } @@ -238,22 +218,22 @@ export class AgentServiceController { //DONE @MessagePattern({ cmd: 'agent-sign-transaction' }) - async signTransaction(payload: { url: string; orgId: string; signEndorsementPayload: object }): Promise { + async signTransaction(payload: { url: string, orgId: string, signEndorsementPayload: object }): Promise { return this.agentServiceService.signTransaction(payload.url, payload.orgId, payload.signEndorsementPayload); } //DONE @MessagePattern({ cmd: 'agent-submit-transaction' }) - async submitTransaction(payload: { url: string; orgId: string; submitEndorsementPayload: object }): Promise { + async submitTransaction(payload: { url: string; orgId: string, submitEndorsementPayload: object }): Promise { return this.agentServiceService.sumbitTransaction(payload.url, payload.orgId, payload.submitEndorsementPayload); } //DONE @MessagePattern({ cmd: 'agent-out-of-band-credential-offer' }) async outOfBandCredentialOffer(payload: { - outOfBandIssuancePayload: IOutOfBandCredentialOffer; - url: string; - orgId: string; + outOfBandIssuancePayload: IOutOfBandCredentialOffer, + url: string, + orgId: string, }): Promise { return this.agentServiceService.outOfBandCredentialOffer( payload.outOfBandIssuancePayload, @@ -263,32 +243,32 @@ export class AgentServiceController { } @MessagePattern({ cmd: 'delete-wallet' }) - async deleteWallet(payload: { orgId; user }): Promise { + async deleteWallet(payload: { orgId, user }): Promise { return this.agentServiceService.deleteWallet(payload.orgId, payload.user); } @MessagePattern({ cmd: 'agent-receive-invitation-url' }) - async receiveInvitationUrl(payload: { url; orgId; receiveInvitationUrl }): Promise { + async receiveInvitationUrl(payload: { url, orgId, receiveInvitationUrl }): Promise { return this.agentServiceService.receiveInvitationUrl(payload.receiveInvitationUrl, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-receive-invitation' }) - async receiveInvitation(payload: { url; orgId; receiveInvitation }): Promise { + async receiveInvitation(payload: { url, orgId, receiveInvitation }): Promise { return this.agentServiceService.receiveInvitation(payload.receiveInvitation, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-send-question' }) - async sendQuestion(payload: { url; orgId; questionPayload }): Promise { + async sendQuestion(payload: { url, orgId, questionPayload }): Promise { return this.agentServiceService.sendQuestion(payload.questionPayload, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-send-basic-message' }) - async sendBasicMessage(payload: { url; orgId; content }): Promise { + async sendBasicMessage(payload: { url, orgId, content }): Promise { return this.agentServiceService.sendBasicMessage(payload.content, payload.url, payload.orgId); } @MessagePattern({ cmd: 'agent-get-question-answer-record' }) - async getQuestionAnswersRecord(payload: { url: string; orgId: string }): Promise { + async getQuestionAnswersRecord(payload: { url: string, orgId: string }): Promise { return this.agentServiceService.getQuestionAnswersRecord(payload.url, payload.orgId); } @@ -297,11 +277,16 @@ export class AgentServiceController { return this.agentServiceService.createSecp256k1KeyPair(payload.orgId); } + @MessagePattern({ cmd: 'ethereum-create-keys' }) + async createEthKeyPair(payload: { orgId: string }): Promise { + return this.agentServiceService.createEthereumKeyPair(payload.orgId); + } + @MessagePattern({ cmd: 'agent-create-connection-invitation' }) async createConnectionInvitation(payload: { - url: string; - orgId: string; - connectionPayload: ICreateConnectionInvitation; + url: string, + orgId: string, + connectionPayload: ICreateConnectionInvitation, }): Promise { return this.agentServiceService.createConnectionInvitation(payload.url, payload.orgId, payload.connectionPayload); } @@ -313,14 +298,29 @@ export class AgentServiceController { */ @MessagePattern({ cmd: 'agent-configure' }) async agentConfigure(payload: { - agentConfigureDto: IAgentConfigure; - user: IUserRequestInterface; + agentConfigureDto: IAgentConfigure, + user: IUserRequestInterface, }): Promise { return this.agentServiceService.agentConfigure(payload.agentConfigureDto, payload.user); } - @MessagePattern({ cmd: 'get-agent-details-by-org-id' }) - async agentdetailsByOrgId(payload: { orgId: string }): Promise { - return this.agentServiceService.getAgentDetails(payload.orgId); + /** + * Sign data from agent + * @param payload + * @returns Signed data by agent + */ + @MessagePattern({ cmd: 'sign-data-from-agent' }) + async signData(payload: { data: SignDataDto; orgId: string }): Promise { + return this.agentServiceService.signDataFromAgent(payload.data, payload.orgId); + } + + /** + * Verify signature on a payload from agent + * @param payload + * @returns Get agent health + */ + @MessagePattern({ cmd: 'verify-signature-from-agent' }) + async verifySignature(payload: { data: VerifySignatureDto; orgId: string }): Promise { + return this.agentServiceService.verifySignature(payload.data, payload.orgId); } } diff --git a/apps/agent-service/src/agent-service.module.ts b/apps/agent-service/src/agent-service.module.ts index d8fe46193..c2fca43bd 100644 --- a/apps/agent-service/src/agent-service.module.ts +++ b/apps/agent-service/src/agent-service.module.ts @@ -11,19 +11,11 @@ import { ConnectionRepository } from 'apps/connection/src/connection.repository' import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; import { UserActivityRepository } from 'libs/user-activity/repositories'; -import { CommonConstants, MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { NATSClient } from '@credebl/common/NATSClient'; - +import { CommonConstants } from '@credebl/common/common.constant'; @Module({ imports: [ ConfigModule.forRoot(), - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, ClientsModule.register([ { name: 'NATS_CLIENT', @@ -42,12 +34,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; Logger, ConnectionService, ConnectionRepository, - UserActivityRepository, - { - provide: MICRO_SERVICE_NAME, - useValue: 'Agent-service' - }, - NATSClient + UserActivityRepository ], exports: [AgentServiceService, AgentServiceRepository, AgentServiceModule] }) diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index fb41a1801..18eb38649 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -54,8 +54,7 @@ import { IAgentStore, IAgentConfigure, OrgDid, - IBasicMessage, - WalletDetails + IBasicMessage } from './interface/agent-service.interface'; import { AgentSpinUpStatus, AgentType, DidMethod, Ledgers, OrgAgentType, PromiseResult } from '@credebl/enum/enum'; import { AgentServiceRepository } from './repositories/agent-service.repository'; @@ -70,16 +69,16 @@ import { WebSocketGateway } from '@nestjs/websockets'; import * as retry from 'async-retry'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { IProofPresentationDetails } from '@credebl/common/interfaces/verification.interface'; +import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { ledgerName } from '@credebl/common/cast.helper'; import { InvitationMessage } from '@credebl/common/interfaces/agent-service.interface'; import * as CryptoJS from 'crypto-js'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { PrismaService } from '@credebl/prisma-service'; -import { from } from 'rxjs'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { SignDataDto } from '../../api-gateway/src/agent-service/dto/agent-service.dto'; +import { SignDataDto, VerifySignatureDto } from 'apps/api-gateway/src/agent-service/dto/agent-service.dto'; import { IVerificationMethod } from 'apps/organization/interfaces/organization.interface'; -import { getAgentUrl } from '@credebl/common/common.utils'; + @Injectable() @WebSocketGateway() export class AgentServiceService { @@ -89,29 +88,16 @@ export class AgentServiceService { private readonly agentServiceRepository: AgentServiceRepository, private readonly prisma: PrismaService, private readonly commonService: CommonService, - // TODO: Remove duplicate, unused variable private readonly connectionService: ConnectionService, @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, - // TODO: Remove duplicate, unused variable @Inject(CACHE_MANAGER) private cacheService: Cache, - private readonly userActivityRepository: UserActivityRepository, - private readonly natsClient: NATSClient + private readonly userActivityRepository: UserActivityRepository ) {} async ReplaceAt(input, search, replace, start, end): Promise { return input.slice(0, start) + input.slice(start, end).replace(search, replace) + input.slice(end); } - async getAgentDetails(orgId: string): Promise { - try { - const agentDetails = await this.agentServiceRepository.getAgentDetailsByOrgId(orgId); - return agentDetails; - } catch (error) { - this.logger.error(`in getAgentDetails ::: ${JSON.stringify(error)}`); - throw new RpcException(error.response ?? error); - } - } - /** * Spinup the agent by organization * @param agentSpinupDto @@ -424,7 +410,7 @@ export class AgentServiceService { credoImage: process.env.AFJ_VERSION || '', protocol: process.env.AGENT_PROTOCOL || '', tenant: agentSpinupDto.tenant || false, - apiKey: process.env.AGENT_API_KEY + apiKey: agentSpinupDto.apiKey }; return walletProvisionPayload; } @@ -528,17 +514,9 @@ export class AgentServiceService { socket.emit('did-publish-process-initiated', { clientId: agentSpinupDto.clientSocketId }); socket.emit('invitation-url-creation-started', { clientId: agentSpinupDto.clientSocketId }); } - const agentBaseWalletToken = await this.commonService.getBaseAgentToken( - agentDetails.agentEndPoint, - agentDetails?.agentToken - ); - if (!agentBaseWalletToken) { - throw new BadRequestException(ResponseMessages.agent.error.baseWalletToken, { - cause: new Error(), - description: ResponseMessages.errorMessages.badRequest - }); - } - const encryptedToken = await this.tokenEncryption(agentBaseWalletToken); + + const encryptedToken = await this.tokenEncryption(agentDetails?.agentToken); + const agentPayload: IStoreOrgAgentDetails = { agentEndPoint, seed: agentSpinupDto.seed, @@ -578,6 +556,9 @@ export class AgentServiceService { socket.emit('did-publish-process-completed', { clientId: agentSpinupDto.clientSocketId }); } + const getOrganization = await this.agentServiceRepository.getOrgDetails(orgData?.id); + + await this._createConnectionInvitation(orgData?.id, user, getOrganization.name); if (agentSpinupDto.clientSocketId) { socket.emit('invitation-url-creation-success', { clientId: agentSpinupDto.clientSocketId }); } @@ -605,11 +586,21 @@ export class AgentServiceService { async _storeOrgAgentDetails(payload: IStoreOrgAgentDetails): Promise { try { - const orgAgentTypeId = await this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.DEDICATED); + /** + * Get orgaization agent type and agent details + */ + const [agentDid, orgAgentTypeId] = await Promise.all([ + this._getAgentDid(payload), + this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.DEDICATED) + ]); + /** + * Get DID method by agent + */ + const getDidMethod = await this._getDidMethod(payload, agentDid); /** * Organization storage data */ - const storeOrgAgentData = this._buildStoreOrgAgentData(payload, `${orgAgentTypeId}`); + const storeOrgAgentData = await this._buildStoreOrgAgentData(payload, getDidMethod, `${orgAgentTypeId}`); /** * Store org agent details */ @@ -621,10 +612,39 @@ export class AgentServiceService { } } - private _buildStoreOrgAgentData(payload: IStoreOrgAgentDetails, orgAgentTypeId: string): IStoreOrgAgentDetails { + private async _getAgentDid(payload: IStoreOrgAgentDetails): Promise { + const { agentEndPoint, apiKey, ledgerId, seed, keyType, method, network, role, did } = payload; + const writeDid = 'write-did'; + const ledgerDetails = await this.agentServiceRepository.getGenesisUrl(ledgerId); + const agentDidWriteUrl = `${agentEndPoint}${CommonConstants.URL_AGENT_WRITE_DID}`; + return this._retryAgentSpinup(agentDidWriteUrl, apiKey, writeDid, seed, keyType, method, network, role, did); + } + + private async _getDidMethod(payload: IStoreOrgAgentDetails, agentDid: object): Promise { + const { agentEndPoint, apiKey, seed, keyType, method, network, role } = payload; + const getDidDoc = 'get-did-doc'; + const getDidMethodUrl = `${agentEndPoint}${CommonConstants.URL_AGENT_GET_DID}/${agentDid['did']}`; + return this._retryAgentSpinup( + getDidMethodUrl, + apiKey, + getDidDoc, + seed, + keyType, + method, + network, + role, + `${agentDid['did']}` + ); + } + + private _buildStoreOrgAgentData( + payload: IStoreOrgAgentDetails, + getDidMethod: object, + orgAgentTypeId: string + ): IStoreOrgAgentDetails { return { - did: '', - verkey: '', + did: getDidMethod['didDocument']?.id, + verkey: getDidMethod['didDocument']?.verificationMethod[0]?.publicKeyBase58, isDidPublic: true, agentSpinUpStatus: AgentSpinUpStatus.COMPLETED, walletName: payload.walletName, @@ -747,6 +767,7 @@ export class AgentServiceService { const agentStatusResponse = { agentSpinupStatus: AgentSpinUpStatus.PROCESSED }; + const getOrgAgent = await this.agentServiceRepository.getAgentDetails(payload.orgId); if (AgentSpinUpStatus.COMPLETED === getOrgAgent?.agentSpinUpStatus) { @@ -785,23 +806,23 @@ export class AgentServiceService { let ledgerIdData = []; try { let ledger; - const { network } = payload; - if (network) { - ledger = await ledgerName(network); - } else { - ledger = Ledgers.Not_Applicable; - } - - const ledgerList = (await this._getALlLedgerDetails()) as unknown as LedgerListResponse; - const isLedgerExist = ledgerList.response.find((existingLedgers) => existingLedgers.name === ledger); - if (!isLedgerExist) { - throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - ledgerIdData = await this.agentServiceRepository.getLedgerDetails(ledger); + const { network } = payload; + if (network) { + ledger = await ledgerName(network); + } else { + ledger = Ledgers.Not_Applicable; + } + const ledgerList = (await this._getALlLedgerDetails()) as unknown as LedgerListResponse; + const isLedgerExist = ledgerList.response.find((existingLedgers) => existingLedgers.name === ledger); + if (!isLedgerExist) { + throw new BadRequestException(ResponseMessages.agent.error.invalidLedger, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + ledgerIdData = await this.agentServiceRepository.getLedgerDetails(ledger); + const agentSpinUpStatus = AgentSpinUpStatus.PROCESSED; // Create and stored agent details @@ -832,6 +853,7 @@ export class AgentServiceService { const orgAgentTypeId = await this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.SHARED); // Get agent type details const agentTypeId = await this.agentServiceRepository.getAgentTypeId(AgentType.AFJ); + const storeOrgAgentData: IStoreOrgAgentDetails = { did: tenantDetails.DIDCreationOption.did, isDidPublic: true, @@ -844,9 +866,9 @@ export class AgentServiceService { tenantId: tenantDetails.walletResponseDetails['id'], walletName: payload.label, ledgerId: ledgerIdData.map((item) => item.id), - id: agentProcess?.id, - apiKey: await this.commonService.dataEncryption(tenantDetails.walletResponseDetails['token']) + id: agentProcess?.id }; + // Get organization data const getOrganization = await this.agentServiceRepository.getOrgDetails(payload.orgId); @@ -932,12 +954,18 @@ export class AgentServiceService { } const getApiKey = await this.getOrgAgentApiKey(orgId); - const url = this.constructUrl(agentDetails); + const getOrgAgentType = await this.agentServiceRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + + const url = this.constructUrl(agentDetails, getOrgAgentType); if (createDidPayload.method === DidMethod.POLYGON) { createDidPayload.endpoint = agentDetails.agentEndPoint; } + if (createDidPayload.method === DidMethod.ETHEREUM) { + createDidPayload.endpoint = agentDetails.agentEndPoint; + } + const { isPrimaryDid, ...payload } = createDidPayload; const didDetails = await this.getDidDetails(url, payload, getApiKey); const getDidByOrg = await this.agentServiceRepository.getOrgDid(orgId); @@ -977,8 +1005,12 @@ export class AgentServiceService { } } - private constructUrl(agentDetails): string { - return `${agentDetails.agentEndPoint}${CommonConstants.URL_AGENT_WRITE_DID}`; + private constructUrl(agentDetails, getOrgAgentType): string { + if (getOrgAgentType.agent === OrgAgentType.DEDICATED) { + return `${agentDetails.agentEndPoint}${CommonConstants.URL_AGENT_WRITE_DID}`; + } else if (getOrgAgentType.agent === OrgAgentType.SHARED) { + return `${agentDetails.agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_DID}${agentDetails.tenantId}`; + } } private async getDidDetails(url, payload, apiKey): Promise { @@ -1057,7 +1089,7 @@ export class AgentServiceService { const getDcryptedToken = await this.commonService.decryptPassword(platformAdminSpinnedUp?.org_agents[0].apiKey); const url = `${getPlatformAgentEndPoint}${CommonConstants.CREATE_POLYGON_SECP256k1_KEY}`; - this.logger.log(`Creating Secp256k1 key pair at URL: ${url}`); + const createKeyPairResponse = await this.commonService.httpPost( url, {}, @@ -1070,6 +1102,33 @@ export class AgentServiceService { } } + + /** + * @returns ethereum key pair for Ethr DID + */ + async createEthereumKeyPair(orgId: string): Promise { + try { + const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent( + CommonConstants.PLATFORM_ADMIN_ORG + ); + + const getPlatformAgentEndPoint = platformAdminSpinnedUp.org_agents[0].agentEndPoint; + const getDcryptedToken = await this.commonService.decryptPassword(platformAdminSpinnedUp?.org_agents[0].apiKey); + + const url = `${getPlatformAgentEndPoint}${CommonConstants.CREATE_ETH_KEY}`; + + const createKeyPairResponse = await this.commonService.httpPost( + url, + {}, + { headers: { authorization: getDcryptedToken } } + ); + return createKeyPairResponse; + } catch (error) { + this.logger.error(`error in create ethereum KeyPair : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + private async getPlatformAdminAndNotify(clientSocketId: string | undefined): Promise { const socket = await this.createSocketInstance(); if (clientSocketId) { @@ -1119,13 +1178,14 @@ export class AgentServiceService { platformAdminSpinnedUp.org_agents[0].agentEndPoint, getDcryptedToken ); - if (!walletResponseDetails && !walletResponseDetails.id && !walletResponseDetails.token) { + if (!walletResponseDetails && !walletResponseDetails.id) { throw new InternalServerErrorException('Error while creating the wallet'); } const didCreateOption = { didPayload: WalletSetupPayload, agentEndpoint: platformAdminSpinnedUp.org_agents[0].agentEndPoint, - apiKey: walletResponseDetails.token + apiKey: getDcryptedToken, + tenantId: walletResponseDetails.id }; const DIDCreationOption = await this._createDID(didCreateOption); if (!DIDCreationOption) { @@ -1158,47 +1218,21 @@ export class AgentServiceService { return tenantDetails; } - private async handleCreateDid( - agentEndpoint: string, - didPayload: Record, - apiKey: string - ): Promise { - try { - return await this.commonService.httpPost(`${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`, didPayload, { - headers: { authorization: apiKey } - }); - } catch (error) { - this.logger.error('Error creating did:', error.message || error); - throw new RpcException(error.response ? error.response : error); - } - } - /** * Create tenant wallet on the agent * @param _createDID * @returns Get tanant status */ private async _createDID(didCreateOption): Promise { - const { didPayload, agentEndpoint, apiKey } = didCreateOption; + const { didPayload, agentEndpoint, apiKey, tenantId } = didCreateOption; // Invoke an API request from the agent to create multi-tenant agent - - //To Do : this is a temporary fix in normal case the api should return correct data in first attempt , to be removed in future on fixing did/write api response - const retryOptions = { - retries: 2 - }; - - const didDetails = await retry(async () => { - const data = await this.handleCreateDid(agentEndpoint, didPayload, apiKey); - if (data?.didDocument || data?.didDoc) { - return data; - } - - throw new Error('Invalid response, retrying...'); - }, retryOptions); - + const didDetails = await this.commonService.httpPost( + `${agentEndpoint}${CommonConstants.URL_SHAGENT_CREATE_DID}${tenantId}`, + didPayload, + { headers: { authorization: apiKey } } + ); return didDetails; } - private async createSocketInstance(): Promise { return io(`${process.env.SOCKET_HOST}`, { reconnection: true, @@ -1225,6 +1259,7 @@ export class AgentServiceService { } } + async createSchema(payload: ITenantSchema): Promise { try { const getApiKey = await this.getOrgAgentApiKey(payload.orgId); @@ -1248,7 +1283,7 @@ export class AgentServiceService { }); }); } else if (OrgAgentType.SHARED === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_CREATE_SCHEMA}`.replace( + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_SCHEMA}`.replace( '#', `${payload.tenantId}` ); @@ -1289,10 +1324,9 @@ export class AgentServiceService { .httpGet(url, { headers: { authorization: getApiKey } }) .then(async (schema) => schema); } else if (OrgAgentType.SHARED === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_GET_SCHEMA_BY_ID}`.replace( - '#', - `${payload.payload.schemaId}` - ); + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_GET_SCHEMA}` + .replace('@', `${payload.payload.schemaId}`) + .replace('#', `${payload.tenantId}`); schemaResponse = await this.commonService .httpGet(url, { headers: { authorization: getApiKey } }) @@ -1322,7 +1356,10 @@ export class AgentServiceService { .httpPost(url, credDefPayload, { headers: { authorization: getApiKey } }) .then(async (credDef) => credDef); } else if (OrgAgentType.SHARED === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_CREATE_CRED_DEF}`; + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_CRED_DEF}`.replace( + '#', + `${payload.tenantId}` + ); const credDefPayload = { tag: payload.payload.tag, schemaId: payload.payload.schemaId, @@ -1354,10 +1391,9 @@ export class AgentServiceService { .httpGet(url, { headers: { authorization: getApiKey } }) .then(async (credDef) => credDef); } else if (OrgAgentType.SHARED === payload.agentType) { - const url = `${payload.agentEndPoint}${CommonConstants.URL_SCHM_GET_CRED_DEF_BY_ID}`.replace( - '#', - `${payload.payload.credentialDefinitionId}` - ); + const url = `${payload.agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CRED_DEF}` + .replace('@', `${payload.payload.credentialDefinitionId}`) + .replace('#', `${payload.tenantId}`); credDefResponse = await this.commonService .httpGet(url, { headers: { authorization: getApiKey } }) .then(async (credDef) => credDef); @@ -1543,109 +1579,6 @@ export class AgentServiceService { } } - /** - * Get agent health - * @param orgId - * @returns agent status - */ - async signDataFromAgent(data: SignDataDto, orgId: string): Promise { - try { - // Get organization agent details - const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); - let agentApiKey; - if (orgAgentDetails) { - agentApiKey = await this.getOrgAgentApiKey(orgId); - } - - if (!orgAgentDetails) { - throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - - if (!orgAgentDetails?.agentEndPoint) { - throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - const url = await getAgentUrl(orgAgentDetails.agentEndPoint, CommonConstants.SIGN_DATA_FROM_AGENT); - - const { dataTypeToSign, credentialPayload, rawPayload, storeCredential } = data; - - if (dataTypeToSign === 'jsonLd' && credentialPayload) { - // Currently, get only primary did for issuance - const diddoc = await this.agentServiceRepository.getOrgDid(orgId, true); - const verificationMethod = diddoc[0].didDocument['verificationMethod'] as IVerificationMethod[]; - // For now, we are strictly restricting dids and verification method associated with the primary did - // We can optionally modify it to be taken from the payload itself - credentialPayload.verificationMethod = verificationMethod[0].id; - } - - const dataToSign = dataTypeToSign === 'jsonLd' ? credentialPayload : rawPayload; - - // Invoke an API request from the agent to assess its current status - const signedDataFromAgent = await this.commonService - .httpPost( - `${url}?dataTypeToSign=${dataTypeToSign}&storeCredential=${storeCredential}`, - { ...dataToSign }, - { - headers: { authorization: agentApiKey } - } - ) - .then(async (response) => response); - - return signedDataFromAgent; - } catch (error) { - this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ?? error); - } - } - - /** - * Get agent health - * @param orgId - * @returns agent status - */ - async verifysignature(data: unknown, orgId: string): Promise { - try { - // Get organization agent details - const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); - let agentApiKey; - if (orgAgentDetails) { - agentApiKey = await this.getOrgAgentApiKey(orgId); - } - - if (!orgAgentDetails) { - throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - - if (!orgAgentDetails?.agentEndPoint) { - throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - const url = await getAgentUrl(orgAgentDetails.agentEndPoint, CommonConstants.VERIFY_SIGNED_DATA_FROM_AGENT); - - // Invoke an API request from the agent to assess its current status - const signedDataFromAgent = await this.commonService - .httpPost(`${url}`, data, { - headers: { authorization: agentApiKey } - }) - .then(async (response) => response); - - return signedDataFromAgent; - } catch (error) { - this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ?? error); - } - } - async getLedgerConfigDetails(user: IUserRequestInterface): Promise { try { const getLedgerConfigData = await this.agentServiceRepository.getLedgerConfigByOrgId(); @@ -1663,6 +1596,7 @@ export class AgentServiceService { ): Promise { try { const getApiKey = await this.getOrgAgentApiKey(orgId); + this.logger.log(`sendOutOfBandProofRequest: payload: ${JSON.stringify(proofRequestPayload)}`); const sendProofRequest = await this.commonService .httpPost(url, proofRequestPayload, { headers: { authorization: getApiKey } }) .then(async (response) => response); @@ -1758,121 +1692,81 @@ export class AgentServiceService { async deleteWallet(orgId: string, user: user): Promise { try { - // Retrieve the API key and agent information - const [getApiKeyResult, orgAgentResult] = await Promise.allSettled([ - this.getOrgAgentApiKey(orgId), - this.agentServiceRepository.getAgentApiKey(orgId) - ]); - - if (orgAgentResult.status === PromiseResult.FULFILLED && !orgAgentResult.value) { - throw new NotFoundException(ResponseMessages.agent.error.walletDoesNotExists); - } - - if (getApiKeyResult.status === PromiseResult.REJECTED) { - throw new InternalServerErrorException(`Failed to get API key: ${getApiKeyResult.reason}`); - } - - if (orgAgentResult.status === PromiseResult.REJECTED) { - throw new InternalServerErrorException(`Failed to get agent information: ${orgAgentResult.reason}`); + // Retrieve the API key and agent information + const [getApiKeyResult, orgAgentResult] = await Promise.allSettled([ + this.getOrgAgentApiKey(orgId), + this.agentServiceRepository.getAgentApiKey(orgId) + ]); + + if (orgAgentResult.status === PromiseResult.FULFILLED && !orgAgentResult.value) { + throw new NotFoundException(ResponseMessages.agent.error.walletDoesNotExists); } - const orgAgent = orgAgentResult?.value; - - const orgAgentTypeResult = await this.agentServiceRepository.getOrgAgentType(orgAgent.orgAgentTypeId); + if (getApiKeyResult.status === PromiseResult.REJECTED) { + throw new InternalServerErrorException(`Failed to get API key: ${getApiKeyResult.reason}`); + } - if (!orgAgentTypeResult) { - throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound); - } + if (orgAgentResult.status === PromiseResult.REJECTED) { + throw new InternalServerErrorException(`Failed to get agent information: ${orgAgentResult.reason}`); + } - let getApiKey; - if (OrgAgentType.SHARED) { - const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent( - CommonConstants.PLATFORM_ADMIN_ORG - ); + const getApiKey = getApiKeyResult?.value; + const orgAgent = orgAgentResult?.value; - getApiKey = await this.commonService.decryptPassword(platformAdminSpinnedUp?.org_agents[0].apiKey); - } else { - getApiKey = getApiKeyResult?.value; - } + const orgAgentTypeResult = await this.agentServiceRepository.getOrgAgentType(orgAgent.orgAgentTypeId); - // Determine the URL based on the agent type - const url = - orgAgentTypeResult.agent === OrgAgentType.SHARED - ? `${orgAgent.agentEndPoint}${CommonConstants.URL_SHAGENT_DELETE_SUB_WALLET}`.replace('#', orgAgent?.tenantId) - : `${orgAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; - - // Perform the deletion in a transaction - return await this.prisma.$transaction(async (prisma) => { - // Delete org agent and related records - const { orgDid, agentInvitation, deleteOrgAgent } = - await this.agentServiceRepository.deleteOrgAgentByOrg(orgId); - - // Make the HTTP DELETE request - const deleteWallet = await this.commonService.httpDelete(url, { - headers: { authorization: getApiKey } - }); - - if (deleteWallet.status !== HttpStatus.NO_CONTENT) { - throw new InternalServerErrorException(ResponseMessages.agent.error.walletNotDeleted); + if (!orgAgentTypeResult) { + throw new NotFoundException(ResponseMessages.agent.error.orgAgentNotFound); } - const deletions = [ - { records: orgDid.count, tableName: 'org_dids' }, - { records: agentInvitation.count, tableName: 'agent_invitations' }, - { records: deleteOrgAgent ? 1 : 0, tableName: 'org_agents' } - ]; + // Determine the URL based on the agent type + const url = + orgAgentTypeResult.agent === OrgAgentType.SHARED + ? `${orgAgent.agentEndPoint}${CommonConstants.URL_SHAGENT_DELETE_SUB_WALLET}`.replace('#', orgAgent?.tenantId) + : `${orgAgent.agentEndPoint}${CommonConstants.URL_DELETE_WALLET}`; + + // Perform the deletion in a transaction + return await this.prisma.$transaction(async (prisma) => { + // Delete org agent and related records + const { orgDid, agentInvitation, deleteOrgAgent } = await this.agentServiceRepository.deleteOrgAgentByOrg(orgId); - const did = orgAgent?.orgDid; + // Make the HTTP DELETE request + const deleteWallet = await this.commonService.httpDelete(url, { + headers: { authorization: getApiKey } + }); - //archive schemas - await this._updateIsSchemaArchivedFlag(did); + if (deleteWallet.status !== HttpStatus.NO_CONTENT) { + throw new InternalServerErrorException(ResponseMessages.agent.error.walletNotDeleted); + } - const logDeletionActivity = async (records, tableName): Promise => { - if (records) { - const txnMetadata = { - deletedRecordsCount: records, - deletedRecordInTable: tableName + const deletions = [ + { records: orgDid.count, tableName: 'org_dids' }, + { records: agentInvitation.count, tableName: 'agent_invitations' }, + { records: deleteOrgAgent ? 1 : 0, tableName: 'org_agents' } + ]; + + const logDeletionActivity = async (records, tableName): Promise => { + if (records) { + const txnMetadata = { + deletedRecordsCount: records, + deletedRecordInTable: tableName + }; + const recordType = RecordType.WALLET; + await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); + } }; - const recordType = RecordType.WALLET; - await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); - } - }; - for (const { records, tableName } of deletions) { - await logDeletionActivity(records, tableName); - } + for (const { records, tableName } of deletions) { + await logDeletionActivity(records, tableName); + } - return deleteOrgAgent; - }); + return deleteOrgAgent; + }); } catch (error) { - this.logger.error(`Error in delete wallet in agent service: ${JSON.stringify(error.message)}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`Error in delete wallet in agent service: ${JSON.stringify(error.message)}`); + throw new RpcException(error.response ? error.response : error); } - } - - async _updateIsSchemaArchivedFlag(did: string): Promise<{ - count: number; - }> { - const pattern = { cmd: 'archive-schemas' }; - - const payload = { - did - }; - const updatedSchemaInfo = await this.agentServiceProxy - .send(pattern, payload) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - return updatedSchemaInfo; - } +} async receiveInvitationUrl(receiveInvitationUrl: IReceiveInvitationUrl, url: string, orgId: string): Promise { try { @@ -1902,68 +1796,29 @@ export class AgentServiceService { async getOrgAgentApiKey(orgId: string): Promise { try { - const orgAgentDetails = await this.agentServiceRepository.getAgentApiKey(orgId); + const orgAgentApiKey = await this.agentServiceRepository.getAgentApiKey(orgId); const orgAgentId = await this.agentServiceRepository.getOrgAgentTypeDetails(OrgAgentType.SHARED); - let agentApiKey; - if ( - orgAgentDetails?.orgAgentTypeId === orgAgentId && - (orgAgentDetails.apiKey === '' || orgAgentDetails.apiKey === null) - ) { + let apiKey; + if (orgAgentApiKey?.orgAgentTypeId === orgAgentId) { const platformAdminSpinnedUp = await this.agentServiceRepository.platformAdminAgent( CommonConstants.PLATFORM_ADMIN_ORG ); if (!platformAdminSpinnedUp) { - throw new InternalServerErrorException(ResponseMessages.agent.error.notConfigured); - } - const walletDetails: WalletDetails = { - agentEndPoint: platformAdminSpinnedUp.org_agents[0]?.agentEndPoint, - apiKey: await this.commonService.decryptPassword(platformAdminSpinnedUp.org_agents[0]?.apiKey), - tenantId: orgAgentDetails.tenantId, - orgId: orgAgentDetails.orgId - }; - const { apiKey } = await this.getTenantToken(walletDetails); - if (!apiKey) { - throw new NotFoundException(ResponseMessages.agent.error.tenantWalletToken, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); + throw new InternalServerErrorException('Agent not able to spin-up'); } - agentApiKey = apiKey; + apiKey = platformAdminSpinnedUp.org_agents[0]?.apiKey; } else { - if (!orgAgentDetails?.apiKey) { - throw new NotFoundException(ResponseMessages.agent.error.apiKeyNotExist); - } - agentApiKey = orgAgentDetails?.apiKey; + apiKey = orgAgentApiKey?.apiKey; } - const decryptedToken = await this.commonService.decryptPassword(agentApiKey); - return decryptedToken; - } catch (error) { - this.logger.error(`Agent api key details : ${JSON.stringify(error)}`); - throw error; - } - } - async getTenantToken(walletDetails: WalletDetails): Promise { - try { - const { agentEndPoint, apiKey, tenantId, orgId } = walletDetails; - if (!agentEndPoint || !apiKey || !tenantId || !orgId) { - throw new BadRequestException(ResponseMessages.agent.error.invalidTenantDetails, { - cause: new Error(), - description: ResponseMessages.errorMessages.badRequest - }); + if (!apiKey) { + throw new NotFoundException(ResponseMessages.agent.error.apiKeyNotExist); } - const tenantWalletToken = await this.commonService.getTenantWalletToken(agentEndPoint, apiKey, tenantId); - if (!tenantWalletToken) { - throw new NotFoundException(ResponseMessages.agent.error.tenantWalletToken, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - const EncryptedTenantToken = await this.tokenEncryption(tenantWalletToken); - const updatedTenantDetails = await this.agentServiceRepository.updateTenantToken(orgId, EncryptedTenantToken); - return updatedTenantDetails; + + const decryptedToken = await this.commonService.decryptPassword(apiKey); + return decryptedToken; } catch (error) { - this.logger.error(`Error in getting org agent type : ${JSON.stringify(error)}`); + this.logger.error(`Agent api key details : ${JSON.stringify(error)}`); throw error; } } @@ -2046,6 +1901,7 @@ export class AgentServiceService { ): Promise { try { const getApiKey = await this.getOrgAgentApiKey(orgId); + const createConnectionInvitation = await this.commonService .httpPost(url, connectionPayload, { headers: { authorization: getApiKey } }) .then(async (response) => response); @@ -2063,7 +1919,8 @@ export class AgentServiceService { response: string; }> { try { - return from(this.natsClient.send(this.agentServiceProxy, pattern, payload)) + return this.agentServiceProxy + .send(pattern, payload) .pipe(map((response) => ({ response }))) .toPromise() .catch((error) => { @@ -2091,4 +1948,174 @@ export class AgentServiceService { throw error; } } + + + /** + * Sign data from agent + * @param orgId + * @returns agent status + */ + async signDataFromAgent(data: SignDataDto, orgId: string): Promise { + try { + // Get organization agent details + const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); + let agentApiKey; + if (orgAgentDetails) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + + if (!orgAgentDetails) { + throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + + if (!orgAgentDetails?.agentEndPoint) { + throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + const orgAgentType = await this.agentServiceRepository.getOrgAgentType(orgAgentDetails?.orgAgentTypeId); + + const url = this.getAgentUrl( + 'sign-data-from-agent', + orgAgentType.agent, + orgAgentDetails.agentEndPoint, + orgAgentDetails.tenantId + ); + + const { dataTypeToSign, credentialPayload, rawPayload, storeCredential } = data; + + // Currently, get only primary did for issuance + const diddoc: OrgDid[] = await this.agentServiceRepository.getOrgDid(orgId); + const verificationMethod = diddoc[0].didDocument['verificationMethod'] as IVerificationMethod[]; + this.logger.debug(`primary did document - diddoc[0]:: ${JSON.stringify(diddoc[0])}`); + if (dataTypeToSign === 'jsonLd' && credentialPayload) { + // For now, we are strictly restricting dids and verification method associated with the primary did + // We can optionally modify it to be taken from the payload itself + credentialPayload.verificationMethod = verificationMethod[0].id; + } + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + if (dataTypeToSign === 'rawData' && rawPayload) { + this.logger.debug(`updating raw data : diddoc[0].didDocument ${JSON.stringify(diddoc[0].didDocument)}`); + rawPayload.did = diddoc[0].didDocument['id']; + rawPayload.keyType = verificationMethod[0].type.toLowerCase().includes('ed25519') ? 'ed25519' : 'k256'; + this.logger.debug(`rawPayload.keyType is set as:: ${rawPayload.keyType}`); + } + const dataToSign = dataTypeToSign === 'jsonLd' ? credentialPayload : rawPayload; + + // Invoke an API request from the agent to assess its current status + const signedDataFromAgent = await this.commonService + .httpPost( + `${url}?dataTypeToSign=${dataTypeToSign}&storeCredential=${storeCredential}`, + { ...dataToSign }, + { + headers: { authorization: agentApiKey } + } + ) + .then(async (response) => response); + + return signedDataFromAgent; + } catch (error) { + this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ?? error); + } + } + + /** + * Verify signature from agent + * @param orgId + * @returns agent status + */ + async verifySignature(data: VerifySignatureDto, orgId: string): Promise { + try { + // Get organization agent details + delete data['orgId']; + this.logger.debug(`In agent-service to verifySignature with data ::: ${JSON.stringify(data)}`); + const orgAgentDetails: org_agents = await this.agentServiceRepository.getOrgAgentDetails(orgId); + let agentApiKey; + if (orgAgentDetails) { + agentApiKey = await this.getOrgAgentApiKey(orgId); + } + + if (!orgAgentDetails) { + throw new NotFoundException(ResponseMessages.agent.error.agentNotExists, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + + if (!orgAgentDetails?.agentEndPoint) { + throw new NotFoundException(ResponseMessages.agent.error.agentUrl, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + const orgAgentType = await this.agentServiceRepository.getOrgAgentType(orgAgentDetails?.orgAgentTypeId); + + const url = this.getAgentUrl( + 'verify-signed-data-from-agent', + orgAgentType.agent, + orgAgentDetails.agentEndPoint, + orgAgentDetails.tenantId + ); + + // Invoke an API request from the agent to assess its current status + const signedDataFromAgent = await this.commonService + .httpPost(`${url}`, data, { + headers: { authorization: agentApiKey } + }) + .then(async (response) => response); + + return signedDataFromAgent; + } catch (error) { + this.logger.error(`Agent signature request details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ?? error); + } + } + + /** + * Description: Fetch agent url + * @param referenceId + * @returns agent URL + */ + getAgentUrl(agentMethodLabel: string, orgAgentType: string, agentEndPoint: string, tenantId: string): string { + try { + let url; + switch (agentMethodLabel) { + case 'sign-data-from-agent': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_AGENT_SIGN_DATA}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHARED_AGENT_SIGN_DATA}`.replace('#', tenantId) + : null; + break; + } + case 'verify-signed-data-from-agent': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_AGENT_VERIFY_SIGNED_DATA}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHARED_AGENT_VERIFY_SIGNED_DATA}`.replace('#', tenantId) + : null; + break; + } + default: { + break; + } + } + if (!url) { + throw new NotFoundException(ResponseMessages.issuance.error.agentUrlNotFound); + } + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } + } + } diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 28b711b93..fbc191561 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -80,7 +80,7 @@ export interface IWallet { } export interface IDidCreate { - keyType: KeyType; + keyType: string; seed: string; domain?: string; network?: string; @@ -343,8 +343,14 @@ export interface IAgentStatus { export interface ISchema { uri: string; } + +export interface IFilter { + type: string; + pattern: string; +} export interface IFields { path: string[]; + filter: IFilter; } export interface IConstraints { fields: IFields[]; @@ -361,7 +367,7 @@ export interface IInputDescriptors { export interface IProofRequestPresentationDefinition { id: string; name: string; - purpose?: string; + purpose: string; input_descriptors: IInputDescriptors[]; } @@ -434,8 +440,6 @@ export interface ICreateTenant { tenantRecord: ITenantRecord; did: string; verkey: string; - didDocument?: Record; - didDoc?: Record; } export interface IOrgAgent { @@ -645,21 +649,16 @@ export interface OrgDid { } export interface ILedgers { - id: string; - createDateTime: Date; - lastChangedDateTime: Date; - name: string; - networkType: string; - poolConfig: string; - isActive: boolean; - networkString: string; - nymTxnEndpoint: string; - indyNamespace: string; - networkUrl: string; -} -export interface WalletDetails { - agentEndPoint: string; - apiKey: string; - tenantId: string; - orgId: string; + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + name: string; + networkType: string; + poolConfig: string; + isActive: boolean; + networkString: string; + nymTxnEndpoint: string; + indyNamespace: string; + networkUrl: string; + } diff --git a/apps/agent-service/src/main.ts b/apps/agent-service/src/main.ts index 292253c1c..8b8b7441f 100644 --- a/apps/agent-service/src/main.ts +++ b/apps/agent-service/src/main.ts @@ -8,7 +8,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; import { Ledgers } from '@credebl/enum/enum'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -19,7 +18,6 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.AGENT_SERVICE, process.env.AGENT_SERVICE_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/agent-service/src/repositories/agent-service.repository.ts b/apps/agent-service/src/repositories/agent-service.repository.ts index f561694e5..09529b573 100644 --- a/apps/agent-service/src/repositories/agent-service.repository.ts +++ b/apps/agent-service/src/repositories/agent-service.repository.ts @@ -1,450 +1,426 @@ import { PrismaService } from '@credebl/prisma-service'; import { ConflictException, Injectable, Logger } from '@nestjs/common'; -/* eslint-disable camelcase */ -import { - Prisma, - ledgerConfig, - ledgers, - org_agents, - org_agents_type, - org_dids, - organisation, - platform_config, - user -} from '@prisma/client'; -import { - ICreateOrgAgent, - ILedgers, - IOrgAgent, - IOrgAgentsResponse, - IOrgLedgers, - IStoreAgent, - IStoreDidDetails, - IStoreOrgAgentDetails, - LedgerNameSpace, - OrgDid -} from '../interface/agent-service.interface'; +// eslint-disable-next-line camelcase +import { Prisma, ledgerConfig, ledgers, org_agents, org_agents_type, org_dids, organisation, platform_config, user } from '@prisma/client'; +import { ICreateOrgAgent, ILedgers, IOrgAgent, IOrgAgentsResponse, IOrgLedgers, IStoreAgent, IStoreDidDetails, IStoreOrgAgentDetails, LedgerNameSpace, OrgDid } from '../interface/agent-service.interface'; import { AgentType, PrismaTables } from '@credebl/enum/enum'; @Injectable() export class AgentServiceRepository { - constructor( - private readonly prisma: PrismaService, - private readonly logger: Logger - ) {} - - /** - * Get platform config details - * @returns - */ - // eslint-disable-next-line camelcase - async getPlatformConfigDetails(): Promise { - try { - return this.prisma.platform_config.findFirst(); - } catch (error) { - this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); - throw error; + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getPlatformConfigDetails(): Promise { + try { + return this.prisma.platform_config.findFirst(); + } catch (error) { + this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); + throw error; + } } - } - async getLedgerConfigByOrgId(): Promise { - try { - const ledgerConfigData = await this.prisma.ledgerConfig.findMany(); - return ledgerConfigData; - } catch (error) { - this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); - throw error; + async getLedgerConfigByOrgId(): Promise { + try { + const ledgerConfigData = await this.prisma.ledgerConfig.findMany(); + return ledgerConfigData; + } catch (error) { + this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); + throw error; + } } - } - /** - * Get genesis url - * @param id - * @returns - */ - async getGenesisUrl(ledgerId: string[]): Promise { - try { - const genesisData = await this.prisma.ledgers.findMany({ - where: { - id: { - in: ledgerId - } + /** + * Get genesis url + * @param id + * @returns + */ + async getGenesisUrl(ledgerId: string[]): Promise { + try { + const genesisData = await this.prisma.ledgers.findMany({ + where: { + id: { + in: ledgerId + } + } + }); + + return genesisData; + } catch (error) { + this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); + throw error; } - }); - - return genesisData; - } catch (error) { - this.logger.error(`[getGenesisUrl] - get genesis URL: ${JSON.stringify(error)}`); - throw error; } - } - /** - * Get organization details - * @param id - * @returns - */ - async getOrgDetails(id: string): Promise { - try { - if (id) { - const oranizationDetails = await this.prisma.organisation.findUnique({ - where: { - id - } - }); - return oranizationDetails; - } - } catch (error) { - this.logger.error(`[getOrgDetails] - get organization details: ${JSON.stringify(error)}`); - throw error; + /** + * Get organization details + * @param id + * @returns + */ + async getOrgDetails(id: string): Promise { + try { + if (id) { + const oranizationDetails = await this.prisma.organisation.findUnique({ + where: { + id + } + }); + return oranizationDetails; + } + } catch (error) { + this.logger.error(`[getOrgDetails] - get organization details: ${JSON.stringify(error)}`); + throw error; + } } - } - // eslint-disable-next-line camelcase - async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { - try { - return this.prisma.org_agents.create({ - data: { - agentSpinUpStatus, - createdBy: userId, - lastChangedBy: userId - }, - select: { - id: true + // eslint-disable-next-line camelcase + async createOrgAgent(agentSpinUpStatus: number, userId: string): Promise { + try { + + return this.prisma.org_agents.create({ + data: { + agentSpinUpStatus, + createdBy: userId, + lastChangedBy: userId + }, + select: { + id: true + } + }); + } catch (error) { + this.logger.error(`[createOrgAgent] - create agent details: ${JSON.stringify(error)}`); + throw error; } - }); - } catch (error) { - this.logger.error(`[createOrgAgent] - create agent details: ${JSON.stringify(error)}`); - throw error; } - } - // eslint-disable-next-line camelcase - async removeOrgAgent(id: string): Promise { - try { - if (id) { - await this.prisma.org_agents.delete({ - where: { - id - } - }); - } - } catch (error) { - this.logger.error(`[removeOrgAgent] - remove org agent details: ${JSON.stringify(error)}`); - throw error; + // eslint-disable-next-line camelcase + async removeOrgAgent(id: string): Promise { + try { + if (id) { + + await this.prisma.org_agents.delete({ + where: { + id + } + }); + } + } catch (error) { + this.logger.error(`[removeOrgAgent] - remove org agent details: ${JSON.stringify(error)}`); + throw error; + } + } - } - // eslint-disable-next-line camelcase - async getAgentDetailsByOrgId(orgId: string): Promise { - try { - const agentDetails = await this.prisma.org_agents.findFirst({ - where: { - orgId + /** + * Store agent details + * @param storeAgentDetails + * @returns + */ + // eslint-disable-next-line camelcase + async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { + try { + const { id, userId, ledgerId, did, didDoc, ...commonFields } = storeOrgAgentDetails; + const firstLedgerId = Array.isArray(ledgerId) ? ledgerId[0] : null; + const data = { + ...commonFields, + ledgerId: firstLedgerId, + createdBy: userId, + lastChangedBy: userId, + didDocument: didDoc, + orgDid: did + }; + + // eslint-disable-next-line camelcase + const query: Promise = id ? + this.prisma.org_agents.update({ + where: { id }, + data + }) : + this.prisma.org_agents.create({ data }); + + return { id: (await query).id }; + } catch (error) { + this.logger.error(`[storeAgentDetails] - store agent details: ${JSON.stringify(error)}`); + throw error; } - }); - return agentDetails; - } catch (error) { - this.logger.error(`[getAgentDetailsByOrgId] - get agent details by orgId: ${JSON.stringify(error)}`); - throw error; } - } - - /** - * Store agent details - * @param storeAgentDetails - * @returns - */ - // eslint-disable-next-line camelcase - async storeOrgAgentDetails(storeOrgAgentDetails: IStoreOrgAgentDetails): Promise { - try { - const { id, userId, ledgerId, did, didDoc, ...commonFields } = storeOrgAgentDetails; - const firstLedgerId = Array.isArray(ledgerId) ? ledgerId[0] : null; - const data = { - ...commonFields, - ledgerId: firstLedgerId, - createdBy: userId, - lastChangedBy: userId, - didDocument: didDoc, - orgDid: did - }; - - // eslint-disable-next-line camelcase - const query: Promise = id - ? this.prisma.org_agents.update({ - where: { id }, - data - }) - : this.prisma.org_agents.create({ data }); - - return { id: (await query).id }; - } catch (error) { - this.logger.error(`[storeAgentDetails] - store agent details: ${JSON.stringify(error)}`); - throw error; + + /** + * Store DID details + * @param storeDidDetails + * @returns did details + */ + // eslint-disable-next-line camelcase + async storeDidDetails(storeDidDetails: IStoreDidDetails): Promise { + try { + const {orgId, did, didDocument, isPrimaryDid, userId, orgAgentId} = storeDidDetails; + + return this.prisma.org_dids.create({ + data: { + orgId, + did, + didDocument, + isPrimaryDid, + createdBy: userId, + lastChangedBy: userId, + orgAgentId + } + }); + } catch (error) { + this.logger.error(`[storeDidDetails] - Store DID details: ${JSON.stringify(error)}`); + throw error; + } } - } - /** - * Store DID details - * @param storeDidDetails - * @returns did details - */ - // eslint-disable-next-line camelcase - async storeDidDetails(storeDidDetails: IStoreDidDetails): Promise { - try { - const { orgId, did, didDocument, isPrimaryDid, userId, orgAgentId } = storeDidDetails; - return this.prisma.org_dids.create({ - data: { - orgId, - did, - didDocument, - isPrimaryDid, - createdBy: userId, - lastChangedBy: userId, - orgAgentId + /** + * Set primary DID + * @param did + * @returns did details + */ + // eslint-disable-next-line camelcase + async setPrimaryDid(orgDid: string, orgId: string, didDocument: Prisma.JsonValue): Promise { + try { + return await this.prisma.org_agents.update({ + where: { + orgId + }, + data: { + orgDid, + didDocument + } + }); + + } catch (error) { + this.logger.error(`[setprimaryDid] - Update DID details: ${JSON.stringify(error)}`); + throw error; } - }); - } catch (error) { - this.logger.error(`[storeDidDetails] - Store DID details: ${JSON.stringify(error)}`); - throw error; } - } - /** - * Set primary DID - * @param did - * @returns did details - */ - // eslint-disable-next-line camelcase - async setPrimaryDid(orgDid: string, orgId: string, didDocument: Prisma.JsonValue): Promise { - try { - return await this.prisma.org_agents.update({ - where: { - orgId - }, - data: { - orgDid, - didDocument + // eslint-disable-next-line camelcase + async updateLedgerId(orgId: string, ledgerId: string): Promise { + try { + return await this.prisma.org_agents.update({ + where: { + orgId + }, + data: { + ledgerId + } + }); + + } catch (error) { + this.logger.error(`[updateLedgerId] - Update ledgerId: ${JSON.stringify(error)}`); + throw error; } - }); - } catch (error) { - this.logger.error(`[setprimaryDid] - Update DID details: ${JSON.stringify(error)}`); - throw error; } - } - // eslint-disable-next-line camelcase - async updateLedgerId(orgId: string, ledgerId: string): Promise { - try { - return await this.prisma.org_agents.update({ - where: { - orgId - }, - data: { - ledgerId + + /** + * Get agent details + * @param orgId + * @returns + */ + // eslint-disable-next-line camelcase + async getAgentDetails(orgId: string): Promise { + try { + + if (orgId) { + + return this.prisma.org_agents.findUnique({ + where: { + orgId + }, + select: { + agentSpinUpStatus: true + } + }); + } + + } catch (error) { + this.logger.error(`[getAgentDetails] - get agent details: ${JSON.stringify(error)}`); + throw error; } - }); - } catch (error) { - this.logger.error(`[updateLedgerId] - Update ledgerId: ${JSON.stringify(error)}`); - throw error; } - } - /** - * Get agent details - * @param orgId - * @returns - */ - // eslint-disable-next-line camelcase - async getAgentDetails(orgId: string): Promise { - try { - if (orgId) { - return this.prisma.org_agents.findUnique({ - where: { - orgId - }, - select: { - agentSpinUpStatus: true - } + // eslint-disable-next-line camelcase + async platformAdminAgent(platformOrg: string): Promise { + return this.prisma.organisation.findFirstOrThrow({ + where: { + name: platformOrg + }, + select: { + // eslint-disable-next-line camelcase + org_agents: { + select: { + agentSpinUpStatus: true, + agentEndPoint: true, + apiKey: true + } + } + } }); - } - } catch (error) { - this.logger.error(`[getAgentDetails] - get agent details: ${JSON.stringify(error)}`); - throw error; } - } - // eslint-disable-next-line camelcase - async platformAdminAgent(platformOrg: string): Promise { - return this.prisma.organisation.findFirstOrThrow({ - where: { - name: platformOrg - }, - select: { - // eslint-disable-next-line camelcase - org_agents: { - select: { - agentSpinUpStatus: true, - agentEndPoint: true, - apiKey: true - } + async getAgentTypeDetails(): Promise { + try { + const { id } = await this.prisma.agents_type.findFirstOrThrow({ + where: { + agent: AgentType.AFJ + } + }); + return id; + } catch (error) { + this.logger.error(`[getAgentTypeDetails] - get org agent health details: ${JSON.stringify(error)}`); + throw error; } - } - }); - } - - async getAgentTypeDetails(): Promise { - try { - const { id } = await this.prisma.agents_type.findFirstOrThrow({ - where: { - agent: AgentType.AFJ - } - }); - return id; - } catch (error) { - this.logger.error(`[getAgentTypeDetails] - get org agent health details: ${JSON.stringify(error)}`); - throw error; } - } - - async getLedgerDetails(name: string[] | string): Promise { - try { - let whereClause; - if (Array.isArray(name)) { - whereClause = { - name: { - in: name - } - }; - } else { - whereClause = { - name - }; - } - - const ledgersDetails = await this.prisma.ledgers.findMany({ - where: whereClause, - select: { - id: true + async getLedgerDetails(name: string[] | string): Promise { + try { + let whereClause; + + if (Array.isArray(name)) { + whereClause = { + name: { + in: name + } + }; + } else { + whereClause = { + name + }; + } + + const ledgersDetails = await this.prisma.ledgers.findMany({ + where: whereClause, + select: { + id: true + } + }); + return ledgersDetails; + } catch (error) { + this.logger.error(`[getLedgerDetails] - get ledger details: ${JSON.stringify(error)}`); + throw error; } - }); - return ledgersDetails; - } catch (error) { - this.logger.error(`[getLedgerDetails] - get ledger details: ${JSON.stringify(error)}`); - throw error; } - } - async getOrgAgentTypeDetails(agentType: string): Promise { - try { - const { id } = await this.prisma.org_agents_type.findFirstOrThrow({ - where: { - agent: agentType + async getOrgAgentTypeDetails(agentType: string): Promise { + try { + const { id } = await this.prisma.org_agents_type.findFirstOrThrow({ + where: { + agent: agentType + } + }); + return id; + } catch (error) { + this.logger.error(`[getOrgAgentTypeDetails] - get org agent type details: ${JSON.stringify(error)}`); + throw error; } - }); - return id; - } catch (error) { - this.logger.error(`[getOrgAgentTypeDetails] - get org agent type details: ${JSON.stringify(error)}`); - throw error; } - } - async getPlatfomOrg(orgName: string): Promise { - try { - const { id } = await this.prisma.organisation.findFirstOrThrow({ - where: { - name: orgName + async getPlatfomOrg(orgName: string): Promise { + try { + const { id } = await this.prisma.organisation.findFirstOrThrow({ + where: { + name: orgName + } + }); + return id; + } catch (error) { + this.logger.error(`[getPlatfomOrg] - get platform org details: ${JSON.stringify(error)}`); + throw error; } - }); - return id; - } catch (error) { - this.logger.error(`[getPlatfomOrg] - get platform org details: ${JSON.stringify(error)}`); - throw error; } - } - async getAgentType(id: string): Promise { - try { - const { agent } = await this.prisma.agents_type.findUnique({ - where: { - id + async getAgentType(id: string): Promise { + try { + const { agent } = await this.prisma.agents_type.findUnique({ + where: { + id + } + }); + return agent; + } catch (error) { + this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); + throw error; } - }); - return agent; - } catch (error) { - this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); - throw error; } - } - async getAgentTypeId(agentType: string): Promise { - try { - const { id } = await this.prisma.agents_type.findFirstOrThrow({ - where: { - agent: agentType + async getAgentTypeId(agentType: string): Promise { + try { + const { id } = await this.prisma.agents_type.findFirstOrThrow({ + where: { + agent: agentType + } + }); + return id; + } catch (error) { + this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); + throw error; } - }); - return id; - } catch (error) { - this.logger.error(`[getAgentType] - get agent type details: ${JSON.stringify(error)}`); - throw error; } - } - /** + /** * Get agent details * @param orgId * @returns Agent health details */ - // eslint-disable-next-line camelcase - async getOrgAgentDetails(orgId: string): Promise { - try { - if (orgId) { - const oranizationAgentDetails = await this.prisma.org_agents.findUnique({ - where: { - orgId - } - }); - return oranizationAgentDetails; - } - } catch (error) { - this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); - throw error; - } - } - - // eslint-disable-next-line camelcase - async getOrgAgentType(orgAgentId: string): Promise { - try { - const orgAgent = await this.prisma.org_agents_type.findUnique({ - where: { - id: orgAgentId + // eslint-disable-next-line camelcase + async getOrgAgentDetails(orgId: string): Promise { + try { + if (orgId) { + + const oranizationAgentDetails = await this.prisma.org_agents.findUnique({ + where: { + orgId + } + }); + return oranizationAgentDetails; + } + } catch (error) { + this.logger.error(`[getOrgAgentDetails] - get org agent health details: ${JSON.stringify(error)}`); + throw error; } - }); - - return orgAgent; - } catch (error) { - this.logger.error(`[getOrgAgentType] - error: ${JSON.stringify(error)}`); - throw error; } - } - async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { - try { - const platformAdminUser = await this.prisma.user.findUnique({ - where: { - email: platformAdminUserEmail + // eslint-disable-next-line camelcase + async getOrgAgentType(orgAgentId: string): Promise { + try { + const orgAgent = await this.prisma.org_agents_type.findUnique({ + where: { + id: orgAgentId + } + }); + + return orgAgent; + + } catch (error) { + this.logger.error(`[getOrgAgentType] - error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getPlatfomAdminUser(platformAdminUserEmail: string): Promise { + try { + const platformAdminUser = await this.prisma.user.findUnique({ + where: { + email: platformAdminUserEmail + } + }); + return platformAdminUser; + } catch (error) { + this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); + throw error; } - }); - return platformAdminUser; - } catch (error) { - this.logger.error(`[getPlatfomAdminUser] - get platform admin user: ${JSON.stringify(error)}`); - throw error; } - } - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase async getAgentApiKey(orgId: string): Promise { try { if (orgId) { @@ -455,6 +431,7 @@ export class AgentServiceRepository { }); return agent; } + } catch (error) { this.logger.error(`[getAgentApiKey] - get api key: ${JSON.stringify(error)}`); throw error; @@ -471,23 +448,20 @@ export class AgentServiceRepository { }); return ledgerDetails; } + } catch (error) { this.logger.error(`[getLedgerByNameSpace] - get indy ledger: ${JSON.stringify(error)}`); throw error; } } - async getOrgDid(orgId: string, isPrimaryDid?: boolean): Promise { + async getOrgDid(orgId: string): Promise { try { - const whereClause: { orgId: string; isPrimaryDid?: boolean } = { orgId }; - if (isPrimaryDid) { - whereClause.isPrimaryDid = isPrimaryDid; - } - const orgDids = await this.prisma.org_dids.findMany({ - where: whereClause + where: { + orgId + } }); - return orgDids; } catch (error) { this.logger.error(`[getOrgDid] - get org DID: ${JSON.stringify(error)}`); @@ -512,85 +486,60 @@ export class AgentServiceRepository { } } - async deleteOrgAgentByOrg(orgId: string): Promise<{ - orgDid: Prisma.BatchPayload; + async deleteOrgAgentByOrg(orgId: string): Promise<{orgDid: Prisma.BatchPayload; agentInvitation: Prisma.BatchPayload; // eslint-disable-next-line camelcase deleteOrgAgent: org_agents; - }> { - const tablesToCheck = [ - `${PrismaTables.CONNECTIONS}`, - `${PrismaTables.CREDENTIALS}`, - `${PrismaTables.PRESENTATIONS}` - ]; + }> { + const tablesToCheck = [ + `${PrismaTables.CONNECTIONS}`, + `${PrismaTables.CREDENTIALS}`, + `${PrismaTables.PRESENTATIONS}`, + `${PrismaTables.ECOSYSTEM_INVITATIONS}`, + `${PrismaTables.ECOSYSTEM_ORGS}` + ]; try { - return await this.prisma.$transaction(async (prisma) => { - const referenceCounts = await Promise.all( - tablesToCheck.map((table) => prisma[table].count({ where: { orgId } })) - ); - - referenceCounts.forEach((count, index) => { - if (0 < count) { - throw new ConflictException(`Organization ID ${orgId} is referenced in the table ${tablesToCheck[index]}`); - } + return await this.prisma.$transaction(async (prisma) => { + const referenceCounts = await Promise.all( + tablesToCheck.map(table => prisma[table].count({ where: { orgId } })) + ); + + referenceCounts.forEach((count, index) => { + if (0 < count) { + throw new ConflictException(`Organization ID ${orgId} is referenced in the table ${tablesToCheck[index]}`); + } + }); + + // Concurrently delete related records + const [orgDid, agentInvitation] = await Promise.all([ + prisma.org_dids.deleteMany({ where: { orgId } }), + prisma.agent_invitations.deleteMany({ where: { orgId } }) + ]); + + // Delete the organization agent + const deleteOrgAgent = await prisma.org_agents.delete({ where: { orgId } }); + + return {orgDid, agentInvitation, deleteOrgAgent}; }); - - // Concurrently delete related records - const [orgDid, agentInvitation] = await Promise.all([ - prisma.org_dids.deleteMany({ where: { orgId } }), - prisma.agent_invitations.deleteMany({ where: { orgId } }) - ]); - - // Delete the organization agent - const deleteOrgAgent = await prisma.org_agents.delete({ where: { orgId } }); - - return { orgDid, agentInvitation, deleteOrgAgent }; - }); } catch (error) { - this.logger.error(`[deleteOrgAgentByOrg] - Error deleting org agent record: ${error.message}`); - throw error; + this.logger.error(`[deleteOrgAgentByOrg] - Error deleting org agent record: ${error.message}`); + throw error; } - } +} - async getLedger(name: string): Promise { - try { - const ledgerData = await this.prisma.ledgers.findFirstOrThrow({ - where: { - name + async getLedger(name: string): Promise { + try { + const ledgerData = await this.prisma.ledgers.findFirstOrThrow({ + where: { + name + } + }); + return ledgerData; + } catch (error) { + this.logger.error(`[getLedger] - get org ledger: ${JSON.stringify(error)}`); + throw error; } - }); - return ledgerData; - } catch (error) { - this.logger.error(`[getLedger] - get org ledger: ${JSON.stringify(error)}`); - throw error; - } - } - - /** - * update tenant token - * @param orgId - * @param token - * @returns updated org_agents - * @throws NotFoundException if the orgId does not exist - * @throws Error if there is an issue updating the org_agents - */ - // eslint-disable-next-line camelcase - async updateTenantToken(orgId: string, token: string): Promise { - try { - const updatedAgent = await this.prisma.org_agents.update({ - where: { - orgId - }, - data: { - apiKey: token - } - }); + } - return updatedAgent; - } catch (error) { - this.logger.error(`[updateTenantToken] - Update tenant records details: ${JSON.stringify(error)}`); - throw error; - } - } -} +} \ No newline at end of file diff --git a/apps/agent-service/test/app.e2e-spec.ts b/apps/agent-service/test/app.e2e-spec.ts index 58f95a822..a3077ccdb 100644 --- a/apps/agent-service/test/app.e2e-spec.ts +++ b/apps/agent-service/test/app.e2e-spec.ts @@ -15,10 +15,8 @@ describe('AgentServiceController (e2e)', () => { await app.init(); }); - it('/ (GET)', () => { - return request(app.getHttpServer()) + it('/ (GET)', () => request(app.getHttpServer()) .get('/') .expect(200) - .expect('Hello World!'); - }); + .expect('Hello World!')); }); diff --git a/apps/api-gateway/common/exception-handler.ts b/apps/api-gateway/common/exception-handler.ts index 674382356..df5928a20 100644 --- a/apps/api-gateway/common/exception-handler.ts +++ b/apps/api-gateway/common/exception-handler.ts @@ -12,6 +12,8 @@ export class CustomExceptionFilter extends BaseExceptionFilter { let errorResponse; let status = HttpStatus.INTERNAL_SERVER_ERROR; + + this.logger.error(`exception ::: ${JSON.stringify(exception)}`); if (!exception || '{}' === JSON.stringify(exception)) { errorResponse = { @@ -22,21 +24,16 @@ export class CustomExceptionFilter extends BaseExceptionFilter { } if (exception instanceof HttpException) { status = exception.getStatus(); - } - let exceptionResponse: ExceptionResponse = {} as ExceptionResponse; - const exceptionResponseData = exception.getResponse ? exception.getResponse() : exception; + let exceptionResponse: ExceptionResponse; - if ('string' === typeof exceptionResponseData) { - exceptionResponse.message = exceptionResponseData; + if (exception['response']) { + exceptionResponse = exception['response']; } else { - exceptionResponse = exceptionResponseData as unknown as ExceptionResponse; + exceptionResponse = exception as unknown as ExceptionResponse; } - if (exceptionResponse.message && exceptionResponse.message.includes(ResponseMessages.nats.error.noSubscribers)) { - exceptionResponse.message = ResponseMessages.nats.error.noSubscribers; - } errorResponse = { statusCode: exceptionResponse.statusCode ? exceptionResponse.statusCode : status, message: exceptionResponse.message @@ -46,6 +43,7 @@ export class CustomExceptionFilter extends BaseExceptionFilter { ? exceptionResponse.error : ResponseMessages.errorMessages.serverError }; + response.status(errorResponse.statusCode).json(errorResponse); } } \ No newline at end of file diff --git a/apps/api-gateway/common/interface.ts b/apps/api-gateway/common/interface.ts index c1791888b..d3869d75f 100644 --- a/apps/api-gateway/common/interface.ts +++ b/apps/api-gateway/common/interface.ts @@ -1,5 +1,3 @@ -import { Prisma } from '@prisma/client'; - export interface ResponseType { statusCode: number; message: string; @@ -8,21 +6,7 @@ export interface ResponseType { } export interface ExceptionResponse { - message: string | string[]; - error: string; - statusCode: number; -} - -export interface ISession { - id?: string; - sessionToken?: string; - userId?: string; - expires?: number; - refreshToken?: string; - keycloakUserId?: string; - type?: string; - accountId?: string; - sessionType?: string; - expiresAt?: Date; - clientInfo?: Prisma.JsonValue | null; + message: string | string[] + error: string + statusCode: number } diff --git a/apps/api-gateway/src/agent-service/agent-service.controller.ts b/apps/api-gateway/src/agent-service/agent-service.controller.ts index f90bbb90a..711f59662 100644 --- a/apps/api-gateway/src/agent-service/agent-service.controller.ts +++ b/apps/api-gateway/src/agent-service/agent-service.controller.ts @@ -20,18 +20,19 @@ import { ApiTags, ApiResponse, ApiOperation, - ApiForbiddenResponse, + ApiUnauthorizedResponse, + ApiForbiddenResponse, ApiBody, - ApiBearerAuth, - ApiUnauthorizedResponse + ApiBearerAuth } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { AgentService } from './agent-service.service'; import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; -import { AgentSpinupDto, SignDataDto, VerifySignatureDto } from './dto/agent-service.dto'; +import { AgentSpinupDto, IVerifySignature, SignDataDto, VerifySignatureDto } from './dto/agent-service.dto'; import { Response } from 'express'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; @@ -41,13 +42,11 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRoles } from 'libs/org-roles/enums'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; -import { Validator } from '@credebl/common/validator'; +import { validateDid } from '@credebl/common/did.validator'; import { CreateWalletDto } from './dto/create-wallet.dto'; import { CreateNewDidDto } from './dto/create-new-did.dto'; import { AgentSpinupValidator, TrimStringParamPipe } from '@credebl/common/cast.helper'; import { AgentConfigureDto } from './dto/agent-configure.dto'; -import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; -import { IVerifySignature } from './interface/agent-service.interface'; const seedLength = 32; @@ -55,34 +54,25 @@ const seedLength = 32; @Controller() @ApiTags('agents') @ApiBearerAuth() -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class AgentController { constructor(private readonly agentService: AgentService) {} private readonly logger = new Logger(); /** * Get Organization agent health - * @param orgId The ID of the organization - * @param reqUser The user making the request - * @param res The response object + * @param orgId + * @param reqUser + * @param res * @returns Get agent details */ @Get('/orgs/:orgId/agents/health') @ApiOperation({ summary: 'Get the agent health details', - description: 'Get the agent health details for the organization' + description: 'Get the agent health details' }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles( - OrgRoles.OWNER, - OrgRoles.ADMIN, - OrgRoles.HOLDER, - OrgRoles.ISSUER, - OrgRoles.SUPER_ADMIN, - OrgRoles.MEMBER, - OrgRoles.VERIFIER - ) + @UseGuards(AuthGuard('jwt')) async getAgentHealth(@Param('orgId') orgId: string, @User() reqUser: user, @Res() res: Response): Promise { const agentData = await this.agentService.getAgentHealth(reqUser, orgId); @@ -95,97 +85,10 @@ export class AgentController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get Organization agent health - * @param orgId The ID of the organization - * @param reqUser The user making the request - * @param res The response object - * @returns Get agent details - */ - @ApiBody({ - description: - 'Enter the data you would like to sign. It can be of type w3c jsonld credential or any type that needs to be signed', - type: SignDataDto, - required: true - }) - @Post('/orgs/:orgId/agents/sign') - @ApiOperation({ - summary: 'Signs data from agent', - description: 'Signs data from agent' - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles( - OrgRoles.OWNER, - OrgRoles.ADMIN, - OrgRoles.HOLDER, - OrgRoles.ISSUER, - OrgRoles.SUPER_ADMIN, - OrgRoles.MEMBER, - OrgRoles.VERIFIER - ) - async signData(@Param('orgId') orgId: string, @Body() data: SignDataDto, @Res() res: Response): Promise { - const agentData = await this.agentService.signData(data, orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.sign, - data: agentData - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Get Organization agent health - * @param orgId The ID of the organization - * @param reqUser The user making the request - * @param res The response object - * @returns Get agent details - */ - @ApiBody({ - description: - 'Enter the data you would like to verify the signature for. It can be of type w3c jsonld credential or any type that needs to be verified', - type: VerifySignatureDto - }) - @Post('/orgs/:orgId/agents/verify-signature') - @ApiOperation({ - summary: 'Validates signed data from agent, including credentials', - description: 'Credentials or any other data signed by the organisation is validated' - }) - // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - // @Roles( - // OrgRoles.OWNER, - // OrgRoles.ADMIN, - // OrgRoles.HOLDER, - // OrgRoles.ISSUER, - // OrgRoles.SUPER_ADMIN, - // OrgRoles.MEMBER, - // OrgRoles.VERIFIER - // ) - async verifysignature( - @Param('orgId') orgId: string, - @Body() data: IVerifySignature, - @Res() res: Response - ): Promise { - const agentData = await this.agentService.verifysignature(data, orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.verify, - data: agentData - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Get the ledger config details - * @param reqUser The user making the request - * @param res The response object - * @returns Ledger config details - */ @Get('/orgs/agents/ledgerConfig') @ApiOperation({ summary: 'Get the ledger config details', - description: 'Get the all supported ledger configuration details for the platform' + description: 'Get the ledger config details' }) @UseGuards(AuthGuard('jwt')) async getLedgerDetails(@User() reqUser: user, @Res() res: Response): Promise { @@ -202,15 +105,14 @@ export class AgentController { /** * Spinup the agent by organization - * @param agentSpinupDto The details of the agent to be spun up - * @param user The user making the request - * @param res The response object + * @param agentSpinupDto + * @param user * @returns Get agent status */ @Post('/orgs/:orgId/agents/spinup') @ApiOperation({ - summary: 'Spinup the platform admin agent', - description: 'Initialize and configure a new platform admin agent for the platform.' + summary: 'Agent spinup', + description: 'Create a new agent spin up.' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -238,20 +140,20 @@ export class AgentController { /** * Create wallet for shared agent - * @param orgId The ID of the organization - * @param createTenantDto The details of the tenant to be created - * @param user The user making the request - * @param res The response object - * @returns Wallet initialization status + * @param orgId + * @param createTenantDto + * @param user + * @param res + * @returns wallet initialization status */ @Post('/orgs/:orgId/agents/wallet') @ApiOperation({ - summary: 'Create Shared Agent Wallet', - description: 'Initialize and create a shared agent wallet for the organization.' + summary: 'Shared Agent', + description: 'Create a shared agent.' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Wallet successfully created', type: ApiResponseDto }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) async createTenant( @Param('orgId') orgId: string, @Body() createTenantDto: CreateTenantDto, @@ -259,6 +161,7 @@ export class AgentController { @Res() res: Response ): Promise { createTenantDto.orgId = orgId; + const tenantDetails = await this.agentService.createTenant(createTenantDto, user); const finalResponse: IResponse = { @@ -272,16 +175,13 @@ export class AgentController { /** * Create wallet - * @param orgId The ID of the organization - * @param createWalletDto The details of the wallet to be created - * @param user The user making the request - * @param res The response object - * @returns Wallet details + * @param orgId + * @returns wallet */ @Post('/orgs/:orgId/agents/createWallet') @ApiOperation({ - summary: 'Create tenant in the agent', - description: 'Create a new wallet for the organization without storing the wallet details in the platform.' + summary: 'Create wallet', + description: 'Create wallet' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -304,18 +204,16 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } + // This function will be used after multiple did method implementation in create wallet /** * Create did - * @param orgId The ID of the organization - * @param createDidDto The details of the DID to be created - * @param user The user making the request - * @param res The response object - * @returns DID details + * @param orgId + * @returns did */ @Post('/orgs/:orgId/agents/did') @ApiOperation({ - summary: 'Create new DID', - description: 'Create a new DID for an organization wallet' + summary: 'Create new did', + description: 'Create new did for an organization' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @@ -326,7 +224,7 @@ export class AgentController { @User() user: user, @Res() res: Response ): Promise { - Validator.validateDid(createDidDto); + await validateDid(createDidDto); if (createDidDto.seed && seedLength !== createDidDto.seed.length) { this.logger.error(`seed must be at most 32 characters.`); @@ -335,29 +233,21 @@ export class AgentController { description: ResponseMessages.errorMessages.badRequest }); } - const didDetails = await this.agentService.createDid(createDidDto, orgId, user); - const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.agent.success.createDid, data: didDetails }; - return res.status(HttpStatus.CREATED).json(finalResponse); } /** * Create Secp256k1 key pair for polygon DID - * @param orgId The ID of the organization - * @param res The response object + * @param orgId * @returns Secp256k1 key pair for polygon DID */ @Post('/orgs/:orgId/agents/polygon/create-keys') - @ApiOperation({ - summary: 'Create Secp256k1 key pair for polygon DID', - description: 'Create Secp256k1 key pair for polygon DID for an organization' - }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.PLATFORM_ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @@ -373,17 +263,37 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * Create + * @param orgId + * @returns Secp256k1 key pair for polygon DID + */ + @Post('/orgs/:orgId/agents/ethereum/create-keys') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.PLATFORM_ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async createEthKeyPair(@Param('orgId') orgId: string, @Res() res: Response): Promise { + const didDetails = await this.agentService.createEthKeyPair(orgId); + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.agent.success.createKeys, + data: didDetails + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); + } + /** * Configure the agent by organization - * @param agentConfigureDto The details of the agent configuration - * @param user The user making the request - * @param res The response object - * @returns Agent configuration status + * @param agentSpinupDto + * @param user + * @returns Get agent status */ @Post('/orgs/:orgId/agents/configure') @ApiOperation({ - summary: 'Configure the organization agent', - description: 'Configure the running dedicated agent for the organization using the provided configuration details.' + summary: 'Agent configure', + description: 'Create a new agent configure.' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -408,32 +318,16 @@ export class AgentController { return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Delete wallet - * @param orgId The ID of the organization - * @param user The user making the request - * @param res The response object - * @returns Success message - */ @Delete('/orgs/:orgId/agents/wallet') @ApiOperation({ - summary: 'Delete agent wallet', - description: 'Delete agent wallet for the organization using orgId.' + summary: 'Delete wallet', + description: 'Delete agent wallet by organization.' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async deleteWallet( - @Param( - 'orgId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', TrimStringParamPipe, new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: user, @Res() res: Response ): Promise { @@ -446,4 +340,87 @@ export class AgentController { return res.status(HttpStatus.OK).json(finalResponse); } + + /** + * Sign data from agent + * @param orgId The ID of the organization + * @param reqUser The user making the request + * @param res The response object + * @returns Get agent details + */ + @ApiBody({ + description: + 'Enter the data you would like to sign. It can be of type w3c jsonld credential or any type that needs to be signed', + type: SignDataDto, + required: true + }) + @Post('/orgs/:orgId/agents/sign') + @ApiOperation({ + summary: 'Signs data from agent', + description: 'Signs data from agent' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles( + OrgRoles.OWNER, + OrgRoles.ADMIN, + OrgRoles.HOLDER, + OrgRoles.ISSUER, + OrgRoles.SUPER_ADMIN, + OrgRoles.MEMBER, + OrgRoles.VERIFIER + ) + async signData(@Param('orgId') orgId: string, @Body() data: SignDataDto, @Res() res: Response): Promise { + const agentData = await this.agentService.signData(data, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.sign, + data: agentData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get Organization agent health + * @param orgId The ID of the organization + * @param reqUser The user making the request + * @param res The response object + * @returns Get agent details + */ + @ApiBody({ + type: VerifySignatureDto, + description: + 'Enter the data you would like to verify the signature for. It can be of type w3c jsonld credential or any type that needs to be verified' + }) + @Post('/orgs/:orgId/agents/verify-signature') + @ApiOperation({ + summary: 'Validates signed data from agent, including credentials', + description: 'Credentials or any other data signed by the organization is validated' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles( + OrgRoles.OWNER, + OrgRoles.ADMIN, + OrgRoles.HOLDER, + OrgRoles.ISSUER, + OrgRoles.SUPER_ADMIN, + OrgRoles.MEMBER, + OrgRoles.VERIFIER + ) + async verifySignature( + @Param('orgId') orgId: string, + @Body() data: IVerifySignature, + @Res() res: Response + ): Promise { + this.logger.log(`[verifySignature] - verifying signature for orgId: ${orgId} with data ${JSON.stringify(data)}`); + const agentData = await this.agentService.verifySignature(data, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.verify, + data: agentData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + } diff --git a/apps/api-gateway/src/agent-service/agent-service.module.ts b/apps/api-gateway/src/agent-service/agent-service.module.ts index 0fa2da620..0d24cd9da 100644 --- a/apps/api-gateway/src/agent-service/agent-service.module.ts +++ b/apps/api-gateway/src/agent-service/agent-service.module.ts @@ -8,7 +8,6 @@ import { AgentController } from './agent-service.controller'; import { AgentService } from './agent-service.service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -25,6 +24,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [AgentController], - providers: [AgentService, CommonService, NATSClient] + providers: [AgentService, CommonService] }) export class AgentModule { } diff --git a/apps/api-gateway/src/agent-service/agent-service.service.ts b/apps/api-gateway/src/agent-service/agent-service.service.ts index ee646dd93..d81c76679 100644 --- a/apps/api-gateway/src/agent-service/agent-service.service.ts +++ b/apps/api-gateway/src/agent-service/agent-service.service.ts @@ -1,104 +1,107 @@ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { user } from '@prisma/client'; import { BaseService } from 'libs/service/base.service'; -import { AgentSpinupDto } from './dto/agent-service.dto'; +import { AgentSpinupDto, IVerifySignature } from './dto/agent-service.dto'; import { CreateTenantDto } from './dto/create-tenant.dto'; import { AgentSpinUpSatus, IWalletRecord } from './interface/agent-service.interface'; import { AgentStatus } from './interface/agent-service.interface'; import { CreateDidDto } from './dto/create-did.dto'; import { CreateWalletDto } from './dto/create-wallet.dto'; import { AgentConfigureDto } from './dto/agent-configure.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class AgentService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('AgentService'); - } - - /** - * Spinup the agent by organization - * @param agentSpinupDto - * @param user - * @returns Get agent status - */ - async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise { - const payload = { agentSpinupDto, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-spinup', payload); - } - - async createTenant(createTenantDto: CreateTenantDto, user: user): Promise { - const payload = { createTenantDto, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-tenant', payload); - } - - async createDid(createDidDto: CreateDidDto, orgId: string, user: user): Promise { - const payload = { createDidDto, orgId, user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-did', payload); - } - - async createWallet(createWalletDto: CreateWalletDto, user: user): Promise { - const payload = { createWalletDto, user }; - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'create-wallet', payload); - } - - async getAgentHealth(user: user, orgId: string): Promise { - const payload = { user, orgId }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-health', payload); - } - - async signData(data: unknown, orgId: string): Promise { - const payload = { data, orgId }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'sign-data-from-agent', payload); - } - - async verifysignature(data: unknown, orgId: string): Promise { - const payload = { data, orgId }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'verify-signature-from-agent', payload); - } - - async getLedgerConfig(user: user): Promise { - const payload = { user }; - - // NATS call - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'get-ledger-config', payload); - } - - async createSecp256k1KeyPair(orgId: string): Promise { - const payload = { orgId }; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'polygon-create-keys', payload); - } - - async agentConfigure(agentConfigureDto: AgentConfigureDto, user: user): Promise { - const payload = { agentConfigureDto, user }; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'agent-configure', payload); - } - - async deleteWallet(orgId: string, user: user): Promise { - const payload = { orgId, user }; - // NATS call - - return this.natsClient.sendNatsMessage(this.agentServiceProxy, 'delete-wallet', payload); - } -} + constructor( + @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy + ) { + super('AgentService'); + } + + /** + * Spinup the agent by organization + * @param agentSpinupDto + * @param user + * @returns Get agent status + */ + async agentSpinup(agentSpinupDto: AgentSpinupDto, user: user): Promise { + const payload = { agentSpinupDto, user }; + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'agent-spinup', payload); + } + + async createTenant(createTenantDto: CreateTenantDto, user: user): Promise { + const payload = { createTenantDto, user }; + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'create-tenant', payload); + } + + async createDid(createDidDto: CreateDidDto, orgId:string, user: user): Promise { + const payload = { createDidDto, orgId, user }; + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'create-did', payload); + } + + async createWallet(createWalletDto: CreateWalletDto, user: user): Promise { + const payload = { createWalletDto, user }; + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'create-wallet', payload); + } + + async getAgentHealth(user: user, orgId:string): Promise { + const payload = { user, orgId }; + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'agent-health', payload); + + } + + async getLedgerConfig(user: user): Promise { + const payload = { user }; + + // NATS call + return this.sendNatsMessage(this.agentServiceProxy, 'get-ledger-config', payload); + } + + async createSecp256k1KeyPair(orgId:string): Promise { + const payload = {orgId}; + // NATS call + + return this.sendNatsMessage(this.agentServiceProxy, 'polygon-create-keys', payload); + } + + async createEthKeyPair(orgId:string): Promise { + const payload = {orgId}; + // NATS call + + return this.sendNatsMessage(this.agentServiceProxy, 'ethereum-create-keys', payload); + } + + async agentConfigure(agentConfigureDto: AgentConfigureDto, user: user): Promise { + const payload = { agentConfigureDto, user }; + // NATS call + + return this.sendNatsMessage(this.agentServiceProxy, 'agent-configure', payload); + } + + async deleteWallet(orgId: string, user: user): Promise { + const payload = { orgId, user }; + // NATS call + + return this.sendNatsMessage(this.agentServiceProxy, 'delete-wallet', payload); + } + + async signData(data: unknown, orgId: string): Promise { + const payload = { data, orgId }; + return this.sendNatsMessage(this.agentServiceProxy, 'sign-data-from-agent', payload); + } + + async verifySignature(data: IVerifySignature, orgId: string): Promise { + const payload = { data, orgId }; + return this.sendNatsMessage(this.agentServiceProxy, 'verify-signature-from-agent', payload); + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts index 140bc5fcd..4cc1d800b 100644 --- a/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts +++ b/apps/api-gateway/src/agent-service/dto/agent-service.dto.ts @@ -4,7 +4,6 @@ import { Transform, Type } from 'class-transformer'; import { IsArray, IsBoolean, - IsEnum, IsIn, IsISO8601, IsNotEmpty, @@ -21,9 +20,9 @@ import { ValidatorConstraintInterface } from 'class-validator'; import { CreateDidDto } from './create-did.dto'; -import { KeyType } from '@credebl/enum/enum'; import { RewriteValidationOptions } from '@credebl/common/custom-overrideable-validation-pipe'; import { BadRequestException } from '@nestjs/common'; + const regex = /^[a-zA-Z0-9 ]*$/; export class AgentSpinupDto extends CreateDidDto { @ApiProperty() @@ -58,16 +57,10 @@ export class AgentSpinupDto extends CreateDidDto { @IsOptional() @IsBoolean() tenant?: boolean; - + orgId: string; } -// class W3cIssuerDto { -// @ApiProperty() -// @IsString() -// id: string; -// } - class W3cCredentialSubjectDto { @ApiPropertyOptional() @IsOptional() @@ -160,9 +153,9 @@ export class SignRawDataDto { @IsString() data: string; - @ApiProperty({ enum: KeyType, enumName: 'KeyType' }) - @IsEnum(KeyType, { message: 'keyType must be a valid KeyType value' }) - keyType: KeyType; + @ApiProperty({ description: 'keyType either ed25519, x25519, k256 or p256' }) + @IsString() + keyType: string; @ApiPropertyOptional({ description: 'Base58-encoded public key' }) @IsOptional() @@ -186,11 +179,11 @@ export class SignRawDataDto { @RewriteValidationOptions({ whitelist: false }) export class SignDataDto { @ApiProperty({ - description: "Type of data being signed. Use 'jsonLd' for W3C credentials or 'rawData' for any other JSON.", + description: 'Type of data being signed. Use \'jsonLd\' for W3C credentials or \'rawData\' for any other JSON.', enum: ['jsonLd', 'rawData'] }) @IsIn(['jsonLd', 'rawData']) - dataTypeToSign: 'jsonLd' | 'rawData'; + dataTypeToSign: 'jsonLd' | 'rawData' = 'rawData'; @ApiProperty({ description: 'Store credential boolean if we want to credential after signing it', @@ -216,84 +209,53 @@ export class SignDataDto { export class VerifySignatureDto { @ApiProperty({ - description: 'This is the signed w3c-jsonLd credential, which we want to verify', - example: { - id: 'http://example.com/credential/9290ae08-8959-4723-a26a-921aab97de6e', - name: 'Teamwork v2', - type: ['VerifiableCredential', 'OpenBadgeCredential'], - image: { - id: 'https://example.com/BadgeDesigns/a99a4121-e04e-477c-a890-15e9af789f73/1c61a4b2-dca4-4684-8f63-221333fd5c86-Teamwork%20v2.png', - type: 'Image' - }, - proof: { - jws: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..PN0gqrfiMhO_2AIT9i3Tv1Qt9Owlh5ZBPnplCrta49-6oyEpHg4aQwjGADe-B2RADb_cvdgVqTJe1G-ds9UYDg', - type: 'Ed25519Signature2018', - created: '2025-06-13T13:47:35Z', - proofPurpose: 'assertionMethod', - verificationMethod: - 'did:key:z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE#z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE' - }, - issuer: { - id: 'did:key:z6MkwSMgqptU9M5AiymSSXv3HLhEhXfE6RonJwMSG2dmURdE' - }, - '@context': [ - 'https://www.w3.org/2018/credentials/v1', - 'https://w3id.org/security/data-integrity/v2', - 'https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json' - ], - validFrom: '2025-06-13T13:47:35.201Z', - awardedDate: '2025-06-12T18:30:00.000Z', - description: - 'EricTech is a cutting-edge product innovation company specialising in Web 3.0 development with extensive expertise in modern technologies. Our unique approach to product invention involves a comprehensive process, starting from the discovery phase and progressing through planning, research, development, marketing, and launch, all tailored to assist businesses in achieving their goals.', - issuanceDate: '2025-06-13T13:47:35.201Z', - expirationDate: '2026-12-30T18:30:00.000Z', - credentialStatus: { - id: 'http://example.com/revocation/addfc5a0-258f-4977-a138-f0d9f33d6dc6', - type: '1EdTechRevocationList' - }, - credentialSubject: { - type: ['AchievementSubject'], - identifier: [ - { - type: 'IdentityObject', - hashed: 'FALSE', - identityHash: 'test@yopmail.com', - identityType: 'emailAddress' - } - ], - achievement: { - id: 'ds', - Tag: ['fghfgh', 'fhfgh'], - Type: ['Achievement'], - name: 'sddgh', - image: { - id: 'sdsd', - type: 'sdd' - }, - version: '2', - criteria: { - narrative: 'sdd' - }, - humanCode: 'sd', - description: 'sdd', - fieldOfStudy: 'sdsd', - specialization: 'fghfg', - achievementType: 'LearningProgram', - otherIdentifier: [ - { - type: 'fhgf', - identifier: 'fghfgh', - identifierType: 'fghfgh' - } - ] - } - } - } + type: String, + description: 'Data to verify for given signature', + example: 'test data' }) - credential: unknown; + data: string; - @ApiPropertyOptional({ default: false }) - @IsOptional() - @IsBoolean() - verifyCredentialStatus?: boolean; + @ApiProperty({ + type: String, + description: 'This is the signature to validate with the given key', + example: 'p256' + }) + keyType?: string; + + @ApiProperty({ + type: String, + description: 'Base58 of public key to verify the signature', + example: 'aSzvTBobJJoaBiwS9aGY76gkxqgNjqEmEvBiRQyBaee' + }) + publicKeyBase58: string; + + @ApiProperty({ + type: String, + description: 'Signature to verify', + example: 'p52JT9PacBjIrv4Zia/RQBgT8OYGTC7dSek66pSIXQRX4ffv06wFBaQ==' + }) + signature: string; + + @ApiProperty({ + type: String, + description: 'DID used during signature to verify the provided signature', + example: 'did:key:z6MkhbXESQ6QqSJZ7g9tzdag7gJ6voBonGnWsEVmvsUBmjfC' + }) + did?: string; + + @ApiProperty({ + type: String, + description: 'DID Method', + example: 'Ed25519' + }) + method?: string; +} + +export interface IVerifySignature { + data: string + keyType?: string + publicKeyBase58: string + signature: string + did?: string + method?: string } diff --git a/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts index 5cf2ca2ff..25511321e 100644 --- a/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts +++ b/apps/api-gateway/src/agent-service/interface/agent-service.interface.ts @@ -1,31 +1,26 @@ export interface AgentSpinUpSatus { - agentSpinupStatus: number; + agentSpinupStatus: number; } export interface AgentStatus { - label: string; - endpoints: string[]; - isInitialized: boolean; + label: string; + endpoints: string[]; + isInitialized: boolean; } interface IWalletConfig { - id: string; - key: string; - keyDerivationMethod: string; + id: string; + key: string; + keyDerivationMethod: string; } interface IConfig { - label: string; - walletConfig: IWalletConfig; + label: string; + walletConfig: IWalletConfig; } export interface IWalletRecord { - _tags: string; - metadata: string; - id: string; - createdAt: string; - config: IConfig; - updatedAt: string; -} - -export interface IVerifySignature { - credential: unknown; - verifyCredentialStatus?: boolean; -} + _tags: string; + metadata: string; + id: string; + createdAt: string; + config: IConfig; + updatedAt: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/agent/agent.controller.ts b/apps/api-gateway/src/agent/agent.controller.ts index defa5baa3..164437c87 100644 --- a/apps/api-gateway/src/agent/agent.controller.ts +++ b/apps/api-gateway/src/agent/agent.controller.ts @@ -16,17 +16,7 @@ import { HttpStatus } from '@nestjs/common'; import { AgentService } from './agent.service'; -import { - ApiTags, - ApiResponse, - ApiOperation, - ApiQuery, - ApiBearerAuth, - ApiParam, - ApiUnauthorizedResponse, - ApiForbiddenResponse, - ApiExcludeEndpoint -} from '@nestjs/swagger'; +import { ApiTags, ApiResponse, ApiOperation, ApiQuery, ApiBearerAuth, ApiParam, ApiUnauthorizedResponse, ApiForbiddenResponse, ApiExcludeEndpoint } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; import { WalletDetailsDto } from '../dtos/wallet-details.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -43,19 +33,17 @@ import { User } from '../authz/decorators/user.decorator'; @ApiBearerAuth() @Controller('agent') export class AgentController { - constructor( - private readonly agentService: AgentService, - private readonly commonService: CommonService - ) {} + constructor(private readonly agentService: AgentService, + private readonly commonService: CommonService) { } private readonly logger = new Logger(); /** - * - * @param user - * @param _public - * @param verkey - * @param did + * + * @param user + * @param _public + * @param verkey + * @param did * @returns List of all the DID created for the current Cloud Agent. */ @Get('/wallet/did') @@ -67,8 +55,8 @@ export class AgentController { @ApiQuery({ name: 'did', required: false }) @ApiOperation({ summary: 'List of all DID', description: 'List of all the DID created for the current Cloud Agent.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) getAllDid( @User() user: any, @Query('_public') _public: boolean, @@ -80,8 +68,8 @@ export class AgentController { } /** - * - * @param user + * + * @param user * @returns Created DID */ @Post('/wallet/did/create') @@ -90,18 +78,20 @@ export class AgentController { @SetMetadata('permissions', [CommonConstants.PERMISSION_ORG_MGMT]) @ApiOperation({ summary: 'Create a new DID', description: 'Create a new did for the current Cloud Agent wallet.' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - createLocalDid(@User() user: any): Promise { + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + createLocalDid( + @User() user: any + ): Promise { this.logger.log(`**** Create Local Did...`); return this.agentService.createLocalDid(user); } /** - * - * @param walletUserDetails - * @param user - * @returns + * + * @param walletUserDetails + * @param user + * @returns */ @Post('/wallet/provision') @ApiTags('agent') @@ -112,9 +102,12 @@ export class AgentController { description: 'Create a new wallet and spin up your Aries Cloud Agent Python by selecting your desired network.' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - walletProvision(@Body() walletUserDetails: WalletDetailsDto, @User() user: object): Promise { + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + walletProvision( + @Body() walletUserDetails: WalletDetailsDto, + @User() user: object + ): Promise { this.logger.log(`**** Spin up the agent...${JSON.stringify(walletUserDetails)}`); const regex = new RegExp('^[a-zA-Z0-9]+$'); @@ -122,30 +115,76 @@ export class AgentController { this.logger.error(`Wallet name in wrong format.`); throw new BadRequestException(`Please enter valid wallet name, It allows only alphanumeric values`); } - const decryptedPassword = this.commonService.decryptPassword(walletUserDetails.walletPassword); + const decryptedPassword = this.commonService.decryptPassword(walletUserDetails.walletPassword); walletUserDetails.walletPassword = decryptedPassword; return this.agentService.walletProvision(walletUserDetails, user); } + /** + * Description: Route for fetch public DID + */ + @Get('/wallet/did/public') + @ApiTags('agent') + @UseGuards(AuthGuard('jwt'), RolesGuard) + @SetMetadata('permissions', [CommonConstants.PERMISSION_ORG_MGMT]) + @ApiOperation({ summary: 'Fetch the current public DID', description: 'Fetch the current public DID.' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + getPublicDid( + @User() user: any + ): Promise { + this.logger.log(`**** Fetch public Did...`); + return this.agentService.getPublicDid(user); + } + + /** + * Description: Route for assign public DID + * @param did + */ + @Get('/wallet/did/public/:id') + @ApiTags('agent') + @UseGuards(AuthGuard('jwt'), RolesGuard) + @SetMetadata('permissions', [CommonConstants.PERMISSION_USER_MANAGEMENT]) + @ApiOperation({ summary: 'Assign public DID', description: 'Assign public DID for the current use.' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + assignPublicDid( + @Param('id') id: number, + @User() user: any + ): Promise { + this.logger.log(`**** Assign public DID...`); + this.logger.log(`user: ${user.orgId} == id: ${Number(id)}`); + + if (user.orgId === Number(id)) { + return this.agentService.assignPublicDid(id, user); + } else { + this.logger.error(`Cannot make DID public of requested organization.`); + throw new BadRequestException(`Cannot make DID public requested organization.`); + } + } + + /** * Description: Route for onboarding register role on ledger - * @param role - * @param alias - * @param verkey - * @param did + * @param role + * @param alias + * @param verkey + * @param did */ @Get('/ledger/register-nym/:id') @ApiTags('agent') @UseGuards(AuthGuard('jwt'), RolesGuard) @SetMetadata('permissions', [CommonConstants.PERMISSION_ORG_MGMT]) - @ApiOperation({ - summary: 'Send a NYM registration to the ledger', - description: 'Write the DID to the ledger to make that DID public.' - }) + @ApiOperation({ summary: 'Send a NYM registration to the ledger', description: 'Write the DID to the ledger to make that DID public.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - registerNym(@Param('id') id: string, @User() user: IUserRequestInterface): Promise { + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + registerNym( + @Param('id') id: string, + @User() user: IUserRequestInterface + ): Promise { this.logger.log(`user: ${typeof user.orgId} == id: ${typeof Number(id)}`); if (user.orgId !== id) { @@ -165,8 +204,8 @@ export class AgentController { description: 'Platform Admin can restart or stop the running Aries Agent. (Platform Admin)' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiParam({ name: 'action', enum: AgentActions }) restartStopAgent(@Param('orgId') orgId: string, @Param('action') action: string): Promise { return this.agentService.restartStopAgent(action, orgId); @@ -181,8 +220,8 @@ export class AgentController { description: 'Fetch the status of the Aries Cloud Agent.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) getAgentServerStatus(@User() user: any): Promise { this.logger.log(`**** getPlatformConfig called...`); return this.agentService.getAgentServerStatus(user); @@ -210,8 +249,8 @@ export class AgentController { description: 'List of all created Aries Cloud Agent status.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiQuery({ name: 'items_per_page', required: false }) @ApiQuery({ name: 'page', required: false }) @ApiQuery({ name: 'search_text', required: false }) @@ -226,6 +265,7 @@ export class AgentController { @Query('status') status: any, @User() user: any ): Promise { + this.logger.log(`status: ${typeof status} ${status}`); items_per_page = items_per_page || 10; diff --git a/apps/api-gateway/src/agent/agent.module.ts b/apps/api-gateway/src/agent/agent.module.ts index a077d3615..224670431 100644 --- a/apps/api-gateway/src/agent/agent.module.ts +++ b/apps/api-gateway/src/agent/agent.module.ts @@ -7,7 +7,6 @@ import { CommonModule } from '../../../../libs/common/src/common.module'; import { CommonService } from '../../../../libs/common/src/common.service'; import { ConfigModule } from '@nestjs/config'; import { commonNatsOptions } from 'libs/service/nats.options'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -22,6 +21,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [AgentController], - providers: [AgentService, CommonService, NATSClient] + providers: [AgentService, CommonService] }) export class AgentModule { } diff --git a/apps/api-gateway/src/agent/agent.service.ts b/apps/api-gateway/src/agent/agent.service.ts index 88bb87b31..d4050ed93 100644 --- a/apps/api-gateway/src/agent/agent.service.ts +++ b/apps/api-gateway/src/agent/agent.service.ts @@ -3,86 +3,98 @@ /* eslint-disable no-return-await */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { Injectable, Inject } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { WalletDetailsDto } from '../dtos/wallet-details.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class AgentService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('AgentService'); - } + constructor( + @Inject('NATS_CLIENT') private readonly agentServiceProxy: ClientProxy + ) { + super('AgentService'); + } + + + /** + * Description: Calling agent service for get-all-did + * @param _public + * @param verkey + * @param did + */ + getAllDid(_public: boolean, verkey: string, did: string, user: any) { + this.logger.log('**** getAllDid called...'); + const payload = { _public, verkey, did, user }; + return this.sendNats(this.agentServiceProxy, 'get-all-did', payload); + } + + /** + * Description: Calling agent service for create-local-did + */ + createLocalDid(user: any) { + this.logger.log('**** createLocalDid called...'); + return this.sendNats(this.agentServiceProxy, 'create-local-did', user); + } + + async walletProvision(walletUserDetails: WalletDetailsDto, user: any) { + this.logger.log(`**** walletProvision called...${JSON.stringify(walletUserDetails)}`); + const payload = { walletUserDetails, user }; + return await this.sendNats(this.agentServiceProxy, 'wallet-provision', payload); + } + + /** + * Description: Calling agent service for get-public-did + */ + getPublicDid(user: any) { + this.logger.log('**** getPublicDid called...'); + return this.sendNats(this.agentServiceProxy, 'get-public-did', user); + } - /** - * Description: Calling agent service for get-all-did - * @param _public - * @param verkey - * @param did - */ - getAllDid(_public: boolean, verkey: string, did: string, user: any) { - this.logger.log('**** getAllDid called...'); - const payload = { _public, verkey, did, user }; - return this.natsClient.sendNats(this.agentServiceProxy, 'get-all-did', payload); - } + /** + * Description: Calling agent service for assign-public-did + * @param did + */ + assignPublicDid(id: number, user: any) { + this.logger.log('**** assignPublicDid called...'); + const payload = { id, user }; + return this.sendNats(this.agentServiceProxy, 'assign-public-did-org', payload); + } - /** - * Description: Calling agent service for create-local-did - */ - createLocalDid(user: any) { - this.logger.log('**** createLocalDid called...'); - return this.natsClient.sendNats(this.agentServiceProxy, 'create-local-did', user); - } - async walletProvision(walletUserDetails: WalletDetailsDto, user: any) { - this.logger.log(`**** walletProvision called...${walletUserDetails.walletName}`); - const payload = { walletUserDetails, user }; - return this.natsClient.sendNats(this.agentServiceProxy, 'wallet-provision', payload); - } + /** + * Description: Calling agent service for onboard-register-ledger + * @param role + * @param alias + * @param verkey + * @param did + */ + registerNym(id: string, user: any) { + this.logger.log('**** registerNym called...'); + const payload = { id, user }; + return this.sendNats(this.agentServiceProxy, 'register-nym-org', payload); + } - /** - * Description: Calling agent service for onboard-register-ledger - * @param role - * @param alias - * @param verkey - * @param did - */ - registerNym(id: string, user: any) { - this.logger.log('**** registerNym called...'); - const payload = { id, user }; - return this.natsClient.sendNats(this.agentServiceProxy, 'register-nym-org', payload); - } + restartStopAgent(action: string, orgId: string) { + const payload = { action, orgId }; + return this.sendNats(this.agentServiceProxy, 'restart-stop-agent', payload); + } - restartStopAgent(action: string, orgId: string) { - const payload = { action, orgId }; - return this.natsClient.sendNats(this.agentServiceProxy, 'restart-stop-agent', payload); - } + getAgentServerStatus(user) { - getAgentServerStatus(user) { - return this.natsClient.sendNats(this.agentServiceProxy, 'get-agent-server-status', user); - } + return this.sendNats(this.agentServiceProxy, 'get-agent-server-status', user); + } - pingServiceAgent() { - this.logger.log('**** pingServiceAgent called...'); - const payload = {}; - return this.natsClient.sendNats(this.agentServiceProxy, 'ping-agent', payload); - } + pingServiceAgent() { + this.logger.log('**** pingServiceAgent called...'); + const payload = {}; + return this.sendNats(this.agentServiceProxy, 'ping-agent', payload); + } - agentSpinupStatus( - items_per_page: number, - page: number, - search_text: string, - agentStatus: string, - sortValue: string, - user: any - ) { - this.logger.log('**** agentSpinupStatus called...'); - const payload = { items_per_page, page, search_text, agentStatus, sortValue, user }; - return this.natsClient.sendNats(this.agentServiceProxy, 'get-agent-spinup-status', payload); - } + agentSpinupStatus(items_per_page: number, page: number, search_text: string, agentStatus: string, sortValue: string, user: any) { + this.logger.log('**** agentSpinupStatus called...'); + const payload = { items_per_page, page, search_text, agentStatus, sortValue, user }; + return this.sendNats(this.agentServiceProxy, 'get-agent-spinup-status', payload); + } } diff --git a/apps/api-gateway/src/app.controller.spec.ts b/apps/api-gateway/src/app.controller.spec.ts index a37961341..0afccf7b9 100644 --- a/apps/api-gateway/src/app.controller.spec.ts +++ b/apps/api-gateway/src/app.controller.spec.ts @@ -3,6 +3,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars let appController: AppController; beforeEach(async () => { diff --git a/apps/api-gateway/src/app.module.ts b/apps/api-gateway/src/app.module.ts index b1af54f4d..42a252563 100644 --- a/apps/api-gateway/src/app.module.ts +++ b/apps/api-gateway/src/app.module.ts @@ -16,27 +16,23 @@ import { VerificationModule } from './verification/verification.module'; import { RevocationController } from './revocation/revocation.controller'; import { RevocationModule } from './revocation/revocation.module'; import { SchemaModule } from './schema/schema.module'; +// import { commonNatsOptions } from 'libs/service/nats.options'; import { UserModule } from './user/user.module'; import { ConnectionModule } from './connection/connection.module'; +import { EcosystemModule } from './ecosystem/ecosystem.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CacheModule } from '@nestjs/cache-manager'; +import * as redisStore from 'cache-manager-redis-store'; import { WebhookModule } from './webhook/webhook.module'; import { UtilitiesModule } from './utilities/utilities.module'; import { NotificationModule } from './notification/notification.module'; import { GeoLocationModule } from './geo-location/geo-location.module'; -import { CommonConstants, MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; +import { CommonConstants } from '@credebl/common/common.constant'; import { CloudWalletModule } from './cloud-wallet/cloud-wallet.module'; -import { ContextModule } from '@credebl/context/contextModule'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; @Module({ imports: [ ConfigModule.forRoot(), - ContextModule, - PlatformConfig, - LoggerModule, ClientsModule.register([ { name: 'NATS_CLIENT', @@ -56,31 +52,24 @@ import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; UserModule, ConnectionModule, IssuanceModule, + EcosystemModule, UtilitiesModule, WebhookModule, NotificationModule, - GlobalConfigModule, - CacheModule.register(), + CacheModule.register({ store: redisStore, host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }), GeoLocationModule, CloudWalletModule ], controllers: [AppController], - providers: [ - AppService, - { - provide: MICRO_SERVICE_NAME, - useValue: 'APIGATEWAY' - } - ], - exports: [CacheModule] + providers: [AppService] }) export class AppModule { configure(userContext: MiddlewareConsumer): void { userContext .apply(AuthzMiddleware) .exclude( - // The below excludes authz with all its subpaths - { path: 'authz/(.*)', method: RequestMethod.ALL }, + { path: 'authz', method: RequestMethod.ALL }, + 'authz/:splat*', 'admin/subscriptions', 'registry/organizations/', 'email/user/verify', diff --git a/apps/api-gateway/src/app.service.ts b/apps/api-gateway/src/app.service.ts index 1ffcaa3cd..095adb165 100644 --- a/apps/api-gateway/src/app.service.ts +++ b/apps/api-gateway/src/app.service.ts @@ -1,10 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; -import { BaseService } from '../../../libs/service/base.service'; import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from '../../../libs/service/base.service'; @Injectable() export class AppService extends BaseService { - constructor(@Inject('NATS_CLIENT') private readonly appServiceProxy: ClientProxy) { - super('appService'); - } + constructor( + @Inject('NATS_CLIENT') private readonly appServiceProxy: ClientProxy + ) { + super('appService'); + } } diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 9fde40303..c46348d45 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -1,60 +1,34 @@ import { - BadRequestException, Body, Controller, - Delete, - ForbiddenException, Get, HttpStatus, Logger, Param, - ParseUUIDPipe, Post, Query, - Req, Res, UnauthorizedException, - UseFilters, - UseGuards + UseFilters } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; -import { - ApiBearerAuth, - ApiBody, - ApiForbiddenResponse, - ApiOperation, - ApiQuery, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; -import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; +import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Response, Request } from 'express'; +import { Response } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; -import { LoginUserDto } from '../user/dto/login-user.dto'; -import { AddUserDetailsDto } from '../user/dto/add-user.dto'; +import { LoginUserDto, LoginUserNameDto } from '../user/dto/login-user.dto'; +import { AddUserDetailsDto, AddUserDetailsUsernameBasedDto } from '../user/dto/add-user.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { ResetPasswordDto } from './dtos/reset-password.dto'; import { ForgotPasswordDto } from './dtos/forgot-password.dto'; import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; -import { getDefaultClient } from '../user/utils'; -import { ClientAliasValidationPipe } from './decorators/user-auth-client'; -import { SessionGuard } from './guards/session.guard'; -import { UserLogoutDto } from './dtos/user-logout.dto'; -import { AuthGuard } from '@nestjs/passport'; -import { ISessionData } from 'apps/user/interfaces/user.interface'; -import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; -import { User } from './decorators/user.decorator'; -import { user } from '@prisma/client'; -import * as useragent from 'express-useragent'; -import { EmptyStringParamPipe, TrimStringParamPipe } from '@credebl/common/cast.helper'; + @Controller('auth') @ApiTags('auth') @@ -62,46 +36,17 @@ import { EmptyStringParamPipe, TrimStringParamPipe } from '@credebl/common/cast. export class AuthzController { private logger = new Logger('AuthzController'); - constructor( - private readonly authzService: AuthzService, - private readonly commonService: CommonService - ) {} + constructor(private readonly authzService: AuthzService, + private readonly commonService: CommonService) { } /** - * Fetch client aliase. - * - * @returns Returns client alias and its url. - */ - @Get('/clientAliases') - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Get client aliases', - description: 'Fetch client aliases and itr url' - }) - async getClientAlias(@Res() res: Response): Promise { - const clientAliases = await this.authzService.getClientAlias(); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchClientAliases, - data: clientAliases - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Verify user’s email address. - * - * @param email The email address of the user. - * @param verificationcode The verification code sent to the user's email. - * @returns Returns the email verification status. + * @param email + * @param verificationcode + * @returns User's email verification status */ @Get('/verify') @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Verify user’s email', - description: 'Checks if the provided verification code is valid for the given email.' - }) + @ApiOperation({ summary: 'Verify user’s email', description: 'Verify user’s email' }) async verifyEmail(@Query() query: EmailVerificationDto, @Res() res: Response): Promise { await this.authzService.verifyEmail(query); const finalResponse: IResponseType = { @@ -110,31 +55,17 @@ export class AuthzController { }; return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Sends a verification email to the user. - * - * @body UserEmailVerificationDto. - * @returns The status of the verification email. - */ + * @param email + * @returns User's verification email sent status + */ @Post('/verification-mail') @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - @ApiQuery({ - name: 'clientAlias', - required: false, - enum: (process.env.SUPPORTED_SSO_CLIENTS || '') - .split(',') - .map((alias) => alias.trim()?.toUpperCase()) - .filter(Boolean) - }) @ApiOperation({ summary: 'Send verification email', description: 'Send verification email to new user' }) - async create( - @Query('clientAlias', ClientAliasValidationPipe) clientAlias: string, - @Body() userEmailVerification: UserEmailVerificationDto, - @Res() res: Response - ): Promise { - userEmailVerification.clientAlias = clientAlias ?? (await getDefaultClient()).alias; + async create(@Body() userEmailVerification: UserEmailVerificationDto, @Res() res: Response): Promise { await this.authzService.sendVerificationMail(userEmailVerification); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -144,53 +75,60 @@ export class AuthzController { } /** - * Registers a new user on the platform. - * - * @body AddUserDetailsDto - * @returns User's registration status and user details - */ + * + * @Body userInfo + * @returns User's registration status and user details + */ @Post('/signup') @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Register new user to platform', - description: 'Register new user to platform with the provided details.' - }) + @ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform' }) async addUserDetails(@Body() userInfo: AddUserDetailsDto, @Res() res: Response): Promise { const userData = await this.authzService.addUserDetails(userInfo); - const finalResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.user.success.create, - data: userData - }; + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userData + }; return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * + * @Body userInfo + * @returns User's registration status and user details + */ + @Post('/username/signup') + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiOperation({ summary: 'Register new user to platform', description: 'Register new user to platform' }) + async addUserDetailsUserNameBased(@Body() userInfo: AddUserDetailsUsernameBasedDto, @Res() res: Response): Promise { + const userData = await this.authzService.addUserDetailsUsernameBased(userInfo); + const finalResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.create, + data: userData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + /** - * Authenticates a user and returns an access token. - * - * @body LoginUserDto - * @returns User's access token details - */ + * @Body loginUserDto + * @returns User's access token details + */ @Post('/signin') @ApiOperation({ summary: 'Authenticate the user for the access', - description: 'Allows registered user to sign.' + description: 'Authenticate the user for the access' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) @ApiBody({ type: LoginUserDto }) - async login(@Req() req: Request, @Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - if (loginUserDto.email) { - const ip = (req.headers['x-forwarded-for'] as string)?.split(',')[0] || req.socket.remoteAddress; - const ua = req.headers['user-agent']; - const expressUa = useragent.parse(ua); - const device = { - browser: `${expressUa.browser} ${expressUa.version ?? ''}`.trim(), - os: expressUa.platform, - deviceType: expressUa.isDesktop ? 'desktop' : 'mobile' - }; + async login(@Body() loginUserDto: LoginUserDto, @Res() res: Response): Promise { - const clientInfo = JSON.stringify({ ...device, rawDetail: ua, ip }); - const userData = await this.authzService.login(clientInfo, loginUserDto.email, loginUserDto.password); + if (loginUserDto.email) { + const userData = await this.authzService.login(loginUserDto.email, loginUserDto.password, loginUserDto.isPasskey); const finalResponse: IResponseType = { statusCode: HttpStatus.OK, @@ -204,243 +142,113 @@ export class AuthzController { } } - /** - * Fetch session details - * - * @returns User's access token details - */ - @Get('/sessionDetails') - @UseGuards(SessionGuard) - @ApiOperation({ - summary: 'Fetch session details', - description: 'Fetch session details against logged in user' - }) - @ApiQuery({ - name: 'sessionId', - required: false - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) - async sessionDetails(@Res() res: Response, @Req() req: Request, @Query() sessionId: ISessionData): Promise { - this.logger.debug(`in authz controller`); - let sessionDetails; - if (0 < Object.keys(sessionId).length) { - sessionDetails = await this.authzService.getSession(sessionId); - } - if (req.user) { - sessionDetails = req.user; + /** + * @Body loginUserDto + * @returns User's access token details + */ + @Post('/username/signin') + @ApiOperation({ + summary: 'Authenticate the user for the access', + description: 'Authenticate the user for the access' + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) + @ApiBody({ type: LoginUserNameDto }) + async usernameLogin(@Body() loginUserDto: LoginUserNameDto, @Res() res: Response): Promise { + + if (loginUserDto.username) { + const userData = await this.authzService.usernameLogin(loginUserDto.username, loginUserDto.password, loginUserDto.isPasskey); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.login, + data: userData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } else { + throw new UnauthorizedException(`Please provide valid credentials`); + } } - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchSession, - data: sessionDetails - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - /** - * Resets user's password. - * - * @body ResetPasswordDto - * @returns The password reset status. - */ @Post('/reset-password') @ApiOperation({ summary: 'Reset password', - description: 'Allows users to reset a new password which should be different form existing password.' + description: 'Reset Password of the user' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async resetPassword(@Body() resetPasswordDto: ResetPasswordDto, @Res() res: Response): Promise { - const userData = await this.authzService.resetPassword(resetPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPassword, - data: userData - }; - return res.status(HttpStatus.OK).json(finalResponse); + + const userData = await this.authzService.resetPassword(resetPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPassword, + data: userData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Initiates the password reset process by sending a reset link to the user's email. - * - * @body ForgotPasswordDto - * @returns Status message indicating whether the reset link was sent successfully. - */ @Post('/forgot-password') @ApiOperation({ summary: 'Forgot password', - description: 'Sends a password reset link to the user’s email.' + description: 'Forgot Password of the user' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto, @Res() res: Response): Promise { - const userData = await this.authzService.forgotPassword(forgotPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPasswordLink, - data: userData - }; - return res.status(HttpStatus.OK).json(finalResponse); + const userData = await this.authzService.forgotPassword(forgotPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPasswordLink, + data: userData + }; + + return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Resets the user's password using a verification token. - * - * @param email The email address of the user. - * @body ResetTokenPasswordDto - * @returns Status message indicating whether the password reset was successful. - */ @Post('/password-reset/:email') @ApiOperation({ - summary: 'Reset password with verification token', - description: 'Resets a user’s password using a verification token sent to their email' + summary: 'Reset password with token', + description: 'Reset Password of the user using token' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async resetNewPassword( @Param('email') email: string, @Body() resetTokenPasswordDto: ResetTokenPasswordDto, - @Res() res: Response - ): Promise { - resetTokenPasswordDto.email = email.trim(); - const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.resetPassword, - data: userData - }; - return res.status(HttpStatus.OK).json(finalResponse); + @Res() res: Response): Promise { + resetTokenPasswordDto.email = email.trim(); + const userData = await this.authzService.resetNewPassword(resetTokenPasswordDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.resetPassword, + data: userData + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Generates a new access token using a refresh token. - * - * @body RefreshTokenDto - * @returns New access token and its details. - */ @Post('/refresh-token') @ApiOperation({ summary: 'Token from refresh token', - description: 'Generates a new access token using a refresh token.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async refreshToken(@Body() refreshTokenDto: RefreshTokenDto, @Res() res: Response): Promise { - const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.refreshToken, - data: tokenData - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Log out user. - * - * @body LogoutUserDto - * @returns Logged out user from current session - */ - @Post('/signout') - @ApiOperation({ - summary: 'Logout user', - description: 'Logout user from current session.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - @ApiBody({ type: UserLogoutDto }) - async logout(@Body() logoutUserDto: UserLogoutDto, @Res() res: Response): Promise { - await this.authzService.logout(logoutUserDto); - - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.logout - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Get all sessions by userId - * @param userId The ID of the user - * @returns All sessions related to the user - */ - @Get('/:userId/sessions') - @ApiOperation({ - summary: 'Get all sessions by userId', - description: 'Retrieve sessions for the user. Based on userId.' + description: 'Get a new token from a refresh token' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) - async userSessions( - @User() reqUser: user, - @Res() res: Response, - @Param( - 'userId', - EmptyStringParamPipe.forParam('userId'), - new TrimStringParamPipe(), - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid user ID`); - } - }) - ) - userId: string - ): Promise { - if (reqUser.id !== userId) { - throw new ForbiddenException('You are not allowed to access sessions of another user'); - } - const response = await this.authzService.userSessions(userId); + async refreshToken( + @Body() refreshTokenDto: RefreshTokenDto, + @Res() res: Response): Promise { + const tokenData = await this.authzService.refreshToken(refreshTokenDto.refreshToken); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.refreshToken, + data: tokenData + }; - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchAllSession, - data: response - }; - return res.status(HttpStatus.OK).json(finalResponse); + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Delete session by sessionId - * @param sessionId The ID of the session record to delete - * @returns Acknowledgement on deletion - */ - @Delete('/:sessionId/sessions') - @ApiOperation({ - summary: 'Delete a particular session using its sessionId', - description: 'Delete a particular session using its sessionId' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt')) - async deleteSession( - @User() reqUser: user, - @Res() res: Response, - @Param( - 'sessionId', - EmptyStringParamPipe.forParam('sessionId'), - new TrimStringParamPipe(), - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid session ID`); - } - }) - ) - sessionId: string - ): Promise { - const response = await this.authzService.deleteSession(sessionId, reqUser.id); - - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: response.message - }; - return res.status(HttpStatus.OK).json(finalResponse); - } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.module.ts b/apps/api-gateway/src/authz/authz.module.ts index de7c42222..bcaca9b44 100644 --- a/apps/api-gateway/src/authz/authz.module.ts +++ b/apps/api-gateway/src/authz/authz.module.ts @@ -1,27 +1,25 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; -import { Logger, Module } from '@nestjs/common'; import { AgentService } from '../agent/agent.service'; import { AuthzController } from './authz.controller'; import { AuthzService } from './authz.service'; -import { CommonConstants } from '@credebl/common/common.constant'; import { CommonModule } from '../../../../libs/common/src/common.module'; import { CommonService } from '../../../../libs/common/src/common.service'; import { ConnectionService } from '../connection/connection.service'; import { HttpModule } from '@nestjs/axios'; import { JwtStrategy } from './jwt.strategy'; import { MobileJwtStrategy } from './mobile-jwt.strategy'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { OrganizationService } from '../organization/organization.service'; +import { Module } from '@nestjs/common'; import { PassportModule } from '@nestjs/passport'; -import { PrismaServiceModule } from '@credebl/prisma-service'; import { SocketGateway } from './socket.gateway'; import { SupabaseService } from '@credebl/supabase'; import { UserModule } from '../user/user.module'; -import { UserRepository } from 'apps/user/repositories/user.repository'; import { UserService } from '../user/user.service'; import { VerificationService } from '../verification/verification.service'; +import { EcosystemService } from '../ecosystem/ecosystem.service'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { OrganizationService } from '../organization/organization.service'; +import { CommonConstants } from '@credebl/common/common.constant'; @Module({ imports: [ @@ -38,26 +36,26 @@ import { getNatsOptions } from '@credebl/common/nats.config'; }, CommonModule ]), - UserModule, - PrismaServiceModule + UserModule ], providers: [ JwtStrategy, AuthzService, MobileJwtStrategy, SocketGateway, - NATSClient, VerificationService, ConnectionService, AgentService, CommonService, UserService, SupabaseService, - OrganizationService, - UserRepository, - Logger + EcosystemService, + OrganizationService + ], + exports: [ + PassportModule, + AuthzService ], - exports: [PassportModule, AuthzService], controllers: [AuthzController] }) -export class AuthzModule {} +export class AuthzModule { } \ No newline at end of file diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index a060788a4..4b8f4e91f 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -1,110 +1,79 @@ -import { Injectable, Inject, HttpException } from '@nestjs/common'; +import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; -import { WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { + WebSocketGateway, + WebSocketServer + +} from '@nestjs/websockets'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; -import { AddUserDetailsDto } from '../user/dto/add-user.dto'; -import { - IClientAliases, - IResetPasswordResponse, - ISignInUser, - ISignUpUserResponse, - IVerifyUserEmail -} from '@credebl/common/interfaces/user.interface'; +import { AddUserDetailsDto, AddUserDetailsUsernameBasedDto } from '../user/dto/add-user.dto'; +import { IResetPasswordResponse, ISendVerificationEmail, ISignInUser, ISignUpUserResponse, IVerifyUserEmail } from '@credebl/common/interfaces/user.interface'; import { ResetPasswordDto } from './dtos/reset-password.dto'; import { ForgotPasswordDto } from './dtos/forgot-password.dto'; import { ResetTokenPasswordDto } from './dtos/reset-token-password'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { user } from '@prisma/client'; -import { IRestrictedUserSession, ISessionDetails } from 'apps/user/interfaces/user.interface'; -import { UserLogoutDto } from './dtos/user-logout.dto'; -import type { Prisma } from '@prisma/client'; -import { ClientProxy } from '@nestjs/microservices'; + @Injectable() @WebSocketGateway() export class AuthzService extends BaseService { //private logger = new Logger('AuthService'); @WebSocketServer() server; constructor( - @Inject('NATS_CLIENT') private readonly authServiceProxy: ClientProxy, - private readonly natsClient: NATSClient + @Inject('NATS_CLIENT') private readonly authServiceProxy: ClientProxy ) { - super('AuthzService'); - } - getClientAlias(): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'get-client-alias-and-url', ''); + super('AuthzService'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any getUserByKeycloakUserId(keycloakUserId: string): Promise { - return this.natsClient.sendNats(this.authServiceProxy, 'get-user-by-keycloakUserId', keycloakUserId); + return this.sendNats(this.authServiceProxy, 'get-user-by-keycloakUserId', keycloakUserId); } - async sendVerificationMail(userEmailVerification: UserEmailVerificationDto): Promise { + async sendVerificationMail(userEmailVerification: UserEmailVerificationDto): Promise { const payload = { userEmailVerification }; - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'send-verification-mail', payload); + return this.sendNatsMessage(this.authServiceProxy, 'send-verification-mail', payload); } async verifyEmail(param: EmailVerificationDto): Promise { const payload = { param }; - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-email-verification', payload); + return this.sendNatsMessage(this.authServiceProxy, 'user-email-verification', payload); } - async login(clientInfo: Prisma.JsonValue, email: string, password?: string, isPasskey = false): Promise { - const payload = { email, password, isPasskey, clientInfo }; - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); + async login(email: string, password?: string, isPasskey = false): Promise { + const payload = { email, password, isPasskey }; + return this.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } - async getSession(sessionId): Promise { - const payload = { ...sessionId }; - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'fetch-session-details', payload); + async usernameLogin(username: string, password?: string, isPasskey = false): Promise { + const payload = { username, password, isPasskey }; + return this.sendNatsMessage(this.authServiceProxy, 'username-holder-login', payload); } - - async checkSession(sessionId): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'check-session-details', sessionId); - } - + async resetPassword(resetPasswordDto: ResetPasswordDto): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-reset-password', resetPasswordDto); + return this.sendNatsMessage(this.authServiceProxy, 'user-reset-password', resetPasswordDto); } - + async forgotPassword(forgotPasswordDto: ForgotPasswordDto): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-forgot-password', forgotPasswordDto); + return this.sendNatsMessage(this.authServiceProxy, 'user-forgot-password', forgotPasswordDto); } async resetNewPassword(resetTokenPasswordDto: ResetTokenPasswordDto): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-set-token-password', resetTokenPasswordDto); + return this.sendNatsMessage(this.authServiceProxy, 'user-set-token-password', resetTokenPasswordDto); } async refreshToken(refreshToken: string): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'refresh-token-details', refreshToken); - } - - async userSessions(userId: string): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'session-details-by-userId', userId); - } - - async deleteSession(sessionId: string, userId: string): Promise<{ message: string }> { - try { - return await this.natsClient.sendNatsMessage(this.authServiceProxy, 'delete-session-by-sessionId', { - sessionId, - userId - }); - } catch (error) { - if (error?.response && error?.status) { - throw new HttpException(error.response, error.status); - } - throw error; - } + return this.sendNatsMessage(this.authServiceProxy, 'refresh-token-details', refreshToken); } async addUserDetails(userInfo: AddUserDetailsDto): Promise { const payload = { userInfo }; - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'add-user', payload); + return this.sendNatsMessage(this.authServiceProxy, 'add-user', payload); } - async logout(logoutUserDto: UserLogoutDto): Promise { - return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-logout', logoutUserDto); + async addUserDetailsUsernameBased(userInfo: AddUserDetailsUsernameBasedDto): Promise { + const payload = { userInfo }; + return this.sendNatsMessage(this.authServiceProxy, 'add-user-username-based', payload); } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/decorators/roles.decorator.ts b/apps/api-gateway/src/authz/decorators/roles.decorator.ts index d6a2bd27b..5fd3237ab 100644 --- a/apps/api-gateway/src/authz/decorators/roles.decorator.ts +++ b/apps/api-gateway/src/authz/decorators/roles.decorator.ts @@ -1,8 +1,12 @@ import { CustomDecorator } from '@nestjs/common'; import { OrgRoles } from 'libs/org-roles/enums'; import { SetMetadata } from '@nestjs/common'; +import { EcosystemRoles } from '@credebl/enum/enum'; export const ROLES_KEY = 'roles'; +export const ECOSYSTEM_ROLES_KEY = 'ecosystem_roles'; export const Roles = (...roles: OrgRoles[]): CustomDecorator => SetMetadata(ROLES_KEY, roles); +export const EcosystemsRoles = (...roles: EcosystemRoles[]): CustomDecorator => SetMetadata(ECOSYSTEM_ROLES_KEY, roles); export const Permissions = (...permissions: string[]): CustomDecorator => SetMetadata('permissions', permissions); export const Subscriptions = (...subscriptions: string[]): CustomDecorator => SetMetadata('subscriptions', subscriptions); + diff --git a/apps/api-gateway/src/authz/decorators/user-auth-client.ts b/apps/api-gateway/src/authz/decorators/user-auth-client.ts deleted file mode 100644 index fd5bf3371..000000000 --- a/apps/api-gateway/src/authz/decorators/user-auth-client.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; - -@Injectable() -export class ClientAliasValidationPipe implements PipeTransform { - private readonly allowedAliases: string[]; - - constructor() { - this.allowedAliases = (process.env.SUPPORTED_SSO_CLIENTS || '') - .split(',') - .map((alias) => alias.trim()) - .filter(Boolean); - } - - transform(value: string): string { - if (!value) { - return value; - } // allow empty if it's optional - const upperValue = value.toUpperCase(); - - if (!this.allowedAliases.includes(upperValue)) { - throw new BadRequestException(`Invalid clientAlias. Allowed values are: ${this.allowedAliases.join(', ')}`); - } - - return upperValue; - } -} diff --git a/apps/api-gateway/src/authz/dtos/auth-token-res.dto.ts b/apps/api-gateway/src/authz/dtos/auth-token-res.dto.ts index 7c7e7a642..7ebe7653c 100644 --- a/apps/api-gateway/src/authz/dtos/auth-token-res.dto.ts +++ b/apps/api-gateway/src/authz/dtos/auth-token-res.dto.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { ApiResponseProperty } from '@nestjs/swagger'; export class AuthTokenResponse { diff --git a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts index f4add5151..9101a7d98 100644 --- a/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts +++ b/apps/api-gateway/src/authz/dtos/forgot-password.dto.ts @@ -1,46 +1,14 @@ -import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; +import { trim } from '@credebl/common/cast.helper'; export class ForgotPasswordDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) - @IsEmail({}, { message: 'Please provide a valid email' }) - @IsNotEmpty({ message: 'Email is required' }) - @IsString({ message: 'Email should be a string' }) - @Transform(({ value }) => trim(value)) - email: string; - - @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsUrl( - { - // eslint-disable-next-line camelcase - require_protocol: true, - // eslint-disable-next-line camelcase - require_tld: true - }, - { message: 'brandLogoUrl should be a valid URL' } - ) - brandLogoUrl?: string; - - @ApiPropertyOptional({ example: 'MyPlatform' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsString({ message: 'platformName should be string' }) - platformName?: string; - - @ApiPropertyOptional({ example: 'https://0.0.0.0:5000' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsString({ message: 'endpoint should be string' }) - endpoint?: string; - - @ApiPropertyOptional({ example: 'VERIFIER' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsString({ message: 'clientAlias should be string' }) - clientAlias?: string; -} + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @IsString({ message: 'Email should be a string' }) + @Transform(({ value }) => trim(value)) + email: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/dtos/reset-password.dto.ts b/apps/api-gateway/src/authz/dtos/reset-password.dto.ts index f2fc18aa0..5b3acf636 100644 --- a/apps/api-gateway/src/authz/dtos/reset-password.dto.ts +++ b/apps/api-gateway/src/authz/dtos/reset-password.dto.ts @@ -5,7 +5,7 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class ResetPasswordDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/authz/dtos/user-logout.dto.ts b/apps/api-gateway/src/authz/dtos/user-logout.dto.ts deleted file mode 100644 index ec836eebb..000000000 --- a/apps/api-gateway/src/authz/dtos/user-logout.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; - -import { ApiPropertyOptional } from '@nestjs/swagger'; - -export class UserLogoutDto { - @ApiPropertyOptional({ - description: 'List of session IDs to log out', - type: [String] - }) - @IsOptional() - @IsArray({ message: 'sessions must be an array' }) - @IsString({ each: true, message: 'each session Id must be a string' }) - @IsNotEmpty({ each: true, message: 'session Id must not be empty' }) - sessions?: string[]; -} diff --git a/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts new file mode 100644 index 000000000..af5b5b619 --- /dev/null +++ b/apps/api-gateway/src/authz/guards/ecosystem-roles.guard.ts @@ -0,0 +1,74 @@ +import { BadRequestException, CanActivate, ExecutionContext, ForbiddenException, Logger, Injectable } from '@nestjs/common'; +import { ECOSYSTEM_ROLES_KEY } from '../decorators/roles.decorator'; +import { Reflector } from '@nestjs/core'; +import { EcosystemService } from '../../ecosystem/ecosystem.service'; +import { EcosystemRoles } from '@credebl/enum/enum'; +import { ResponseMessages } from '@credebl/common/response-messages'; + +@Injectable() +export class EcosystemRolesGuard implements CanActivate { + constructor( + private reflector: Reflector, + private readonly ecosystemService: EcosystemService // Inject the service + ) { } + + + private logger = new Logger('Ecosystem Role Guard'); + async canActivate(context: ExecutionContext): Promise { + const requiredRoles = this.reflector.getAllAndOverride(ECOSYSTEM_ROLES_KEY, [ + context.getHandler(), + context.getClass() + ]); + const requiredRolesNames = Object.values(requiredRoles) as string[]; + + if (!requiredRolesNames) { + return true; + } + + // Request requires org check, proceed with it + const req = context.switchToHttp().getRequest(); + + const { user } = req; + + req.params.ecosystemId = req.params?.ecosystemId ? req.params?.ecosystemId?.trim() : ''; + req.query.ecosystemId = req.query?.ecosystemId ? req.query?.ecosystemId?.trim() : ''; + req.body.ecosystemId = req.body?.ecosystemId ? req.body?.ecosystemId?.trim() : ''; + + const ecosystemId = req.params.ecosystemId || req.query.ecosystemId || req.body.ecosystemId; + + if (!ecosystemId) { + throw new BadRequestException(ResponseMessages.organisation.error.ecosystemIdIsRequired); + } + + if ((req.params.orgId || req.query.orgId || req.body.orgId) + && (req.params.ecosystemId || req.query.ecosystemId || req.body.ecosystemId)) { + + const orgId = req.params.orgId || req.query.orgId || req.body.orgId; + const ecosystemId = req.params.ecosystemId || req.query.ecosystemId || req.body.ecosystemId; + + + const ecosystemOrgData = await this.ecosystemService.fetchEcosystemOrg(ecosystemId, orgId); + + if (!ecosystemOrgData) { + throw new ForbiddenException(ResponseMessages.organisation.error.orgDoesNotMatch); + } + + user.ecosystemOrgRole = ecosystemOrgData['ecosystemRole']['name']; + + if (!user.ecosystemOrgRole) { + throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemRoleNotMatch); + } + + } else { + throw new BadRequestException(ResponseMessages.ecosystem.error.orgEcoIdRequired); + } + + // Sending user friendly message if a user attempts to access an API that is inaccessible to their role + const roleAccess = requiredRoles.some((role) => user.ecosystemOrgRole === role); + if (!roleAccess) { + throw new ForbiddenException(ResponseMessages.organisation.error.roleNotMatch, { cause: new Error(), description: ResponseMessages.errorMessages.forbidden }); + } + + return roleAccess; + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/authz/guards/session.guard.ts b/apps/api-gateway/src/authz/guards/session.guard.ts deleted file mode 100644 index 2a8977bd3..000000000 --- a/apps/api-gateway/src/authz/guards/session.guard.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; - -import { Request } from 'express'; -import { UserRepository } from 'apps/user/repositories/user.repository'; - -@Injectable() -export class SessionGuard implements CanActivate { - constructor(private readonly userRepository: UserRepository) {} - - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest(); - const sessionId = request.cookies['session_id']; - if (sessionId) { - const user = await this.userRepository.validateSession(sessionId); - request.user = user; - } - return true; - } -} diff --git a/apps/api-gateway/src/authz/guards/user-role.guard.ts b/apps/api-gateway/src/authz/guards/user-role.guard.ts index 3c3dc4d7a..c6a40bd23 100644 --- a/apps/api-gateway/src/authz/guards/user-role.guard.ts +++ b/apps/api-gateway/src/authz/guards/user-role.guard.ts @@ -8,14 +8,13 @@ export class UserRoleGuard implements CanActivate { const { user } = request; - if (!user?.userRole) { + if (!user?.realm_access.roles) { throw new ForbiddenException('This role is not a holder.'); } - if (!user?.userRole.includes('holder')) { + if (!user?.realm_access.roles.includes('holder')) { throw new ForbiddenException('This role is not a holder.'); } - return true; } } diff --git a/apps/api-gateway/src/authz/jwt-payload.interface.ts b/apps/api-gateway/src/authz/jwt-payload.interface.ts index da946d04f..c5bdf6928 100644 --- a/apps/api-gateway/src/authz/jwt-payload.interface.ts +++ b/apps/api-gateway/src/authz/jwt-payload.interface.ts @@ -1,13 +1,15 @@ export interface JwtPayload { - iss: string; - sub: string; - aud: string[]; - iat?: number; - exp?: number; - azp: string; - scope: string; - gty?: string; - permissions: string[]; - email?: string; - sid: string; -} + iss: string; + sub: string; + aud: string[]; + iat?: number; + exp?: number; + azp: string; + scope: string; + gty?: string; + permissions: string[]; + email?: string; + preferred_username: string; + client_id: string; + } + \ No newline at end of file diff --git a/apps/api-gateway/src/authz/jwt.strategy.ts b/apps/api-gateway/src/authz/jwt.strategy.ts index 5120fd914..8c2e0df20 100644 --- a/apps/api-gateway/src/authz/jwt.strategy.ts +++ b/apps/api-gateway/src/authz/jwt.strategy.ts @@ -1,18 +1,17 @@ import * as dotenv from 'dotenv'; -import * as jwt from 'jsonwebtoken'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { Injectable, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { Injectable, Logger, UnauthorizedException, NotFoundException } from '@nestjs/common'; -import { AuthzService } from './authz.service'; -import { CommonConstants } from '@credebl/common/common.constant'; -import { IOrganization } from '@credebl/common/interfaces/organization.interface'; import { JwtPayload } from './jwt-payload.interface'; -import { OrganizationService } from '../organization/organization.service'; import { PassportStrategy } from '@nestjs/passport'; -import { ResponseMessages } from '@credebl/common/response-messages'; import { UserService } from '../user/user.service'; +import * as jwt from 'jsonwebtoken'; import { passportJwtSecret } from 'jwks-rsa'; +import { CommonConstants } from '@credebl/common/common.constant'; +import { OrganizationService } from '../organization/organization.service'; +import { IOrganization } from '@credebl/common/interfaces/organization.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; dotenv.config(); @@ -22,15 +21,15 @@ export class JwtStrategy extends PassportStrategy(Strategy) { constructor( private readonly usersService: UserService, - private readonly organizationService: OrganizationService, - private readonly authzService: AuthzService - ) { + private readonly organizationService: OrganizationService + ) { + super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKeyProvider: async (request, jwtToken, done) => { - // Todo: We need to add this logic in seprate jwt gurd to handle the token expiration functionality. + secretOrKeyProvider: (request, jwtToken, done) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const decodedToken: any = jwt.decode(jwtToken); + if (!decodedToken) { throw new UnauthorizedException(ResponseMessages.user.error.invalidAccessToken); } @@ -50,38 +49,26 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }); }, algorithms: ['RS256'] - }); + }); } async validate(payload: JwtPayload): Promise { + let userDetails = null; let userInfo; - const sessionId = payload?.sid; - let sessionDetails = null; - if (sessionId) { - try { - sessionDetails = await this.authzService.checkSession(sessionId); - } catch (error) { - this.logger.log('Error in JWT Stratergy while fetching session details', JSON.stringify(error, null, 2)); - } - if (!sessionDetails) { - throw new UnauthorizedException(ResponseMessages.user.error.invalidAccessToken); - } - } - - if (payload?.email) { - userInfo = await this.usersService.getUserByUserIdInKeycloak(payload?.email); + if (payload && !(payload.preferred_username.includes('service-account'))) { + userInfo = await this.usersService.getUserByUserIdInKeycloak(payload?.preferred_username); } - + if (payload.hasOwnProperty('client_id')) { const orgDetails: IOrganization = await this.organizationService.findOrganizationOwner(payload['client_id']); - + this.logger.log('Organization details fetched'); if (!orgDetails) { throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - + // eslint-disable-next-line prefer-destructuring const userOrgDetails = 0 < orgDetails.userOrgRoles.length && orgDetails.userOrgRoles[0]; @@ -96,10 +83,11 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }); this.logger.log('User details set'); + } else { userDetails = await this.usersService.findUserinKeycloak(payload.sub); } - + if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.notFound); } diff --git a/apps/api-gateway/src/authz/mobile-jwt.strategy.ts b/apps/api-gateway/src/authz/mobile-jwt.strategy.ts index be5b49fc7..a45366563 100644 --- a/apps/api-gateway/src/authz/mobile-jwt.strategy.ts +++ b/apps/api-gateway/src/authz/mobile-jwt.strategy.ts @@ -2,13 +2,12 @@ import * as dotenv from 'dotenv'; import * as jwt from 'jsonwebtoken'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { BadRequestException, Injectable, Logger, UnauthorizedException } from '@nestjs/common'; +import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; import { CommonConstants } from '@credebl/common/common.constant'; import { PassportStrategy } from '@nestjs/passport'; import { passportJwtSecret } from 'jwks-rsa'; dotenv.config(); -const logger = new Logger(); @Injectable() export class MobileJwtStrategy extends PassportStrategy(Strategy, 'mobile-jwt') { @@ -18,6 +17,7 @@ export class MobileJwtStrategy extends PassportStrategy(Strategy, 'mobile-jwt') super({ secretOrKeyProvider: (request, jwtToken, done) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const decodedToken: any = jwt.decode(jwtToken); const audiance = decodedToken.iss.toString(); const jwtOptions = { @@ -38,6 +38,7 @@ export class MobileJwtStrategy extends PassportStrategy(Strategy, 'mobile-jwt') }); } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type validate(payload: any) { if ('adeyaClient' !== payload.azp) { throw new UnauthorizedException( diff --git a/apps/api-gateway/src/authz/socket.gateway.ts b/apps/api-gateway/src/authz/socket.gateway.ts index 9d7d7e90c..049e3e6fd 100644 --- a/apps/api-gateway/src/authz/socket.gateway.ts +++ b/apps/api-gateway/src/authz/socket.gateway.ts @@ -125,6 +125,6 @@ export class SocketGateway implements OnGatewayConnection { this.logger.log(`bulk-issuance-process-retry-completed ${payload.clientId}`); this.server .to(payload.clientId) - .emit('bulk-issuance-process-retry-completed', {fileUploadId: payload.fileUploadId}); + .emit('bulk-issuance-process-retry-completed'); } -} \ No newline at end of file +} diff --git a/apps/api-gateway/src/cloud-wallet/cloud-wallet.controller.ts b/apps/api-gateway/src/cloud-wallet/cloud-wallet.controller.ts index f581c11f4..f0280d5f4 100644 --- a/apps/api-gateway/src/cloud-wallet/cloud-wallet.controller.ts +++ b/apps/api-gateway/src/cloud-wallet/cloud-wallet.controller.ts @@ -1,40 +1,11 @@ import { IResponse } from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { - Controller, - Post, - Logger, - Body, - HttpStatus, - Res, - UseFilters, - UseGuards, - Get, - Param, - Query, - BadRequestException -} from '@nestjs/common'; -import { - ApiBearerAuth, - ApiForbiddenResponse, - ApiOperation, - ApiQuery, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { Controller, Post, Logger, Body, HttpStatus, Res, UseFilters, UseGuards, Get, Param, Query, BadRequestException, Delete, Patch } from '@nestjs/common'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { CloudWalletService } from './cloud-wallet.service'; -import { - AcceptOfferDto, - BasicMessageDTO, - CreateCloudWalletDidDto, - CreateCloudWalletDto, - CredentialListDto, - GetAllCloudWalletConnectionsDto, - ReceiveInvitationUrlDTO -} from './dtos/cloudWallet.dto'; +import { AcceptOfferDto, AddConnectionTypeDto, BasicMessageDTO, CreateCloudWalletDidDto, CreateCloudWalletDto, CredentialListDto, ExportCloudWalletDto, GetAllCloudWalletConnectionsDto, ReceiveInvitationUrlDTO, UpdateBaseWalletDto } from './dtos/cloudWallet.dto'; import { Response } from 'express'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -43,286 +14,503 @@ import { AuthGuard } from '@nestjs/passport'; import { User } from '../authz/decorators/user.decorator'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; -import { Validator } from '@credebl/common/validator'; +import { validateDid } from '@credebl/common/did.validator'; import { CommonConstants } from '@credebl/common/common.constant'; import { UserRoleGuard } from '../authz/guards/user-role.guard'; import { AcceptProofRequestDto } from './dtos/accept-proof-request.dto'; -import { - IBasicMessage, - IConnectionDetailsById, - ICredentialDetails, - IGetProofPresentation, - IGetProofPresentationById, - IWalletDetailsForDidList -} from '@credebl/common/interfaces/cloud-wallet.interface'; +import { IBasicMessage, IConnectionDetailsById, ICredentialDetails, IGetCredentialsForRequest, IGetProofPresentation, IGetProofPresentationById, IProofPresentationPayloadWithCred, IProofPresentationDetails, IWalletDetailsForDidList, IW3cCredentials, ICheckCloudWalletStatus, IDeleteCloudWallet, IAddConnectionType } from '@credebl/common/interfaces/cloud-wallet.interface'; import { CreateConnectionDto } from './dtos/create-connection.dto'; +import { ProofWithCredDto } from './dtos/accept-proof-request-with-cred.dto'; +import { DeclineProofRequestDto } from './dtos/decline-proof-request.dto'; +import { SelfAttestedCredentialDto } from './dtos/self-attested-credential.dto'; + @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('cloud-wallet') @ApiBearerAuth() -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class CloudWalletController { - private readonly logger = new Logger('cloud-wallet'); - constructor(private readonly cloudWalletService: CloudWalletService) {} - /** - * Configure cloud base wallet - * @param cloudBaseWalletConfigure - * @param user - * @param res - * @returns success message - */ - @Post('/configure/base-wallet') - @ApiOperation({ - summary: 'Configure Cloud Base Wallet', - description: 'Endpoint to configure the base wallet for the cloud wallet service.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Base wallet configured successfully', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt')) - async configureBaseWallet( - @Res() res: Response, - @Body() cloudBaseWalletConfigure: CloudBaseWalletConfigureDto, - @User() user: user - ): Promise { - const { id, email } = user; + private readonly logger = new Logger('cloud-wallet'); + constructor(private readonly cloudWalletService: CloudWalletService + ) { } - cloudBaseWalletConfigure.userId = id; - cloudBaseWalletConfigure.email = email; + /** + * Configure cloud base wallet + * @param cloudBaseWalletConfigure + * @param user + * @param res + * @returns Success message + */ + @Post('/configure/base-wallet') + @ApiOperation({ summary: 'Configure base wallet', description: 'Configure base wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + async configureBaseWallet( + @Res() res: Response, + @Body() cloudBaseWalletConfigure: CloudBaseWalletConfigureDto, + @User() user: user + ): Promise { - const configureBaseWalletData = await this.cloudWalletService.configureBaseWallet(cloudBaseWalletConfigure); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.configureBaseWallet, - data: configureBaseWalletData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + const { id, email } = user; - /** - * Create cloud wallet - * @param cloudWalletDetails - * @param res - * @returns Success message and wallet details - */ - @Post('/create-wallet') - @ApiOperation({ summary: 'Create Cloud Wallet', description: 'Endpoint to create a new cloud wallet for the user.' }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Cloud wallet created successfully', type: ApiResponseDto }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async createCloudWallet( - @Res() res: Response, - @Body() cloudWalletDetails: CreateCloudWalletDto, - @User() user: user - ): Promise { - const { email, id } = user; - cloudWalletDetails.email = email; - cloudWalletDetails.userId = id; - const cloudWalletData = await this.cloudWalletService.createCloudWallet(cloudWalletDetails); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.create, - data: cloudWalletData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + cloudBaseWalletConfigure.userId = id; + cloudBaseWalletConfigure.email = email; - /** - * Accept proof request - * @param acceptProofRequest - * @returns success message - */ - @Post('/proofs/accept-request') - @ApiOperation({ - summary: 'Accept Proof Request', - description: 'Endpoint to accept a proof request for the cloud wallet.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Proof request accepted successfully', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async acceptProofRequest( - @Res() res: Response, - @Body() acceptProofRequest: AcceptProofRequestDto, - @User() user: user - ): Promise { - const { id, email } = user; - acceptProofRequest.userId = id; - acceptProofRequest.email = email; + const configureBaseWalletData = await this.cloudWalletService.configureBaseWallet(cloudBaseWalletConfigure); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.configureBaseWallet, + data: configureBaseWalletData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const acceptProofRequestDetails = await this.cloudWalletService.acceptProofRequest(acceptProofRequest); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.acceptProofRequest, - data: acceptProofRequestDetails - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Create cloud wallet + * @param cloudWalletDetails + * @param res + * @returns Success message and wallet details + */ + @Post('/create-wallet') + @ApiOperation({ summary: 'Create cloud wallet', description: 'Create cloud wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async createCloudWallet( + @Res() res: Response, + @Body() cloudWalletDetails: CreateCloudWalletDto, + @User() user: user + ): Promise { + const {email, id} = user; + cloudWalletDetails.email = email; + cloudWalletDetails.userId = id; + const cloudWalletData = await this.cloudWalletService.createCloudWallet(cloudWalletDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.create, + data: cloudWalletData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + /** + * Delete cloud wallet + * @param res + * @returns Success message + */ + @Delete('/delete-wallet') + @ApiOperation({ summary: 'Delete cloud wallet', description: 'Delete cloud wallet' }) + @ApiResponse({ status: HttpStatus.OK, type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async deleteCloudWallet( + @Res() res: Response, + @User() user: user, + @Query('deleteHolder') deleteHolder: boolean = false + ): Promise { + const {id} = user; + + const cloudWalletDetails: IDeleteCloudWallet = { + userId: id, + deleteHolder + }; + + await this.cloudWalletService.deleteCloudWallet(cloudWalletDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.delete + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } + + + /** + * Check cloud wallet status + * @returns success message + */ + @Get('/check-cloud-wallet-status') + @ApiOperation({ summary: 'Accept proof request', description: 'Accept proof request' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async checkCloudWalletStatus( + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const checkCloudWalletStatus : ICheckCloudWalletStatus = { + userId: id, + email + }; + try { + const checkCloudWalletStatusRes = await this.cloudWalletService.checkCloudWalletStatus(checkCloudWalletStatus); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.checkCloudWalletStatus, + data: checkCloudWalletStatusRes + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } catch (error) { + if ('P2025' === error?.code) { + return res.status(HttpStatus.NOT_FOUND).json({message:'Not found'}); + } + throw error; + } + + + } + + @Get('get-active-base-wallet') + @ApiOperation({ summary: 'Create cloud wallet', description: 'Create cloud wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getBaseWalletDetails( + @Res() res: Response, + @User() user: user + ): Promise { + const baseWalletData = await this.cloudWalletService.getBaseWalletDetails(user); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.getBaseWalletInfo, + data: baseWalletData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Patch('/base-wallet/:walletId') + @ApiOperation({ summary: 'Update base wallet', description: 'Update base wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async updateBaseWalletDetails( + @Param('walletId') walletId:string, + @Body() updateBaseWalletDto:UpdateBaseWalletDto, + @User() user: user, + @Res() res: Response + ): Promise { + + const {email, id} = user; + updateBaseWalletDto.email = email; + updateBaseWalletDto.userId = id; + updateBaseWalletDto.walletId = walletId; + const baseWalletData = await this.cloudWalletService.updateBaseWalletDetails(updateBaseWalletDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.getBaseWalletInfo, + data: baseWalletData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + + /** + * Accept proof request + * @param acceptProofRequest + * @returns success message + */ + @Post('/proofs/accept-request') + @ApiOperation({ summary: 'Accept proof request', description: 'Accept proof request' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async acceptProofRequest( + @Res() res: Response, + @Body() acceptProofRequest: AcceptProofRequestDto, + @User() user: user + ): Promise { + const { id, email } = user; + acceptProofRequest.userId = id; + acceptProofRequest.email = email; + + const acceptProofRequestDetails = await this.cloudWalletService.acceptProofRequest(acceptProofRequest); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.acceptProofRequest, + data: acceptProofRequestDetails + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * Decline proof request + * @param DeclineProofRequest + * @returns success message + */ + @Post('/proofs/decline-request') + @ApiOperation({ summary: 'Accept proof request', description: 'Accept proof request' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async declineProofRequest( + @Res() res: Response, + @Body() declineProofRequest: DeclineProofRequestDto, + @User() user: user + ): Promise { + const { id, email } = user; + declineProofRequest.userId = id; + declineProofRequest.email = email; + + const acceptProofRequestDetails = await this.cloudWalletService.declineProofRequest(declineProofRequest); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.declineProofRequest, + data: acceptProofRequestDetails + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * Get proof presentation by proof id + * @param proofRecordId + * @param res + * @returns success message + */ + @Post('/proofs/acceptRequestWithCred') + @ApiOperation({ summary: 'Get proof presentation by Id', description: 'Get proof presentation by Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async acceptRequestWithCred( + @Body() proofDto: ProofWithCredDto, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + const proofPresentationPayloadWithCred: IProofPresentationPayloadWithCred = { + userId: id, + email, + proof: proofDto + }; + + const proofDetails = await this.cloudWalletService.submitProofWithCred(proofPresentationPayloadWithCred); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.getProofById, + data: proofDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Submit proof presentation + * @param proofRecordId + * @param res + * @returns success message + */ + @Post('/proofs/:proofRecordId') + @ApiOperation({ summary: 'Get proof presentation by Id', description: 'Get proof presentation by Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getProofById( + @Param('proofRecordId') proofRecordId: string, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const proofPresentationByIdPayload: IGetProofPresentationById = { + userId: id, + email, + proofRecordId + }; + + const getProofDetails = await this.cloudWalletService.getProofById(proofPresentationByIdPayload); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.getProofById, + data: getProofDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Get proof presentation by proof id + * Get Credentials for request by proof id * @param proofRecordId * @param res * @returns success message */ - @Get('/proofs/:proofRecordId') - @ApiOperation({ - summary: 'Get Proof Presentation by ID', - description: 'Endpoint to retrieve proof presentation details by proof record ID.' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Proof presentation retrieved successfully', - type: ApiResponseDto - }) + @Get('/credentialsForRequest/:proofRecordId') + @ApiOperation({ summary: 'Get proof presentation by Id', description: 'Get proof presentation by Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getProofById( + async getCredentialsForRequest( @Param('proofRecordId') proofRecordId: string, @Res() res: Response, @User() user: user ): Promise { const { id, email } = user; - const proofPresentationByIdPayload: IGetProofPresentationById = { + const proofPresentationByIdPayload: IGetCredentialsForRequest = { userId: id, email, proofRecordId }; - const getProofDetails = await this.cloudWalletService.getProofById(proofPresentationByIdPayload); + const getProofDetails = await this.cloudWalletService.getCredentialsForRequest(proofPresentationByIdPayload); const finalResponse: IResponse = { statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.getProofById, + message: ResponseMessages.cloudWallet.success.getCredentialsByProofId, data: getProofDetails }; return res.status(HttpStatus.OK).json(finalResponse); } + /** + * Get proof presentations + * @param threadId + * @param res + * @returns success message + */ + @Get('/proofs') + @ApiOperation({ summary: 'Get proof presentation', description: 'Get proof presentation' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + @ApiQuery({ + name: 'threadId', + required: false + }) + async getProofPresentation( + @Res() res: Response, + @User() user: user, + @Query('threadId') threadId?: string + ): Promise { + + const { id, email } = user; + + const proofPresentationPayload: IGetProofPresentation = { + userId: id, + email, + threadId + }; + + const getProofDetails = await this.cloudWalletService.getProofPresentation(proofPresentationPayload); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.getProofPresentation, + data: getProofDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** - * Get proof presentations - * @param threadId + * Get credential Format data by credential id + * @param credentialListQueryOptions * @param res - * @returns success message + * @returns Credential list */ - @Get('/proofs') + @Get('/credentialFormatData/:credentialRecordId') @ApiOperation({ - summary: 'Get Proof Presentations', - description: 'Endpoint to retrieve all proof presentations, optionally filtered by thread ID.' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Proof presentations retrieved successfully', - type: ApiResponseDto + summary: 'Get credential by credential record Id', + description: 'Get credential by credential record Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), UserRoleGuard) - @ApiQuery({ - name: 'threadId', - required: false - }) - async getProofPresentation( + async getCredentialFormatDataByCredentialRecordId( + @Param('credentialRecordId') credentialRecordId: string, @Res() res: Response, - @User() user: user, - @Query('threadId') threadId?: string + @User() user: user ): Promise { const { id, email } = user; - const proofPresentationPayload: IGetProofPresentation = { + const credentialDetails: ICredentialDetails = { userId: id, email, - threadId + credentialRecordId }; - const getProofDetails = await this.cloudWalletService.getProofPresentation(proofPresentationPayload); + const credentialsDetailResponse = await this.cloudWalletService.getCredentialFormatDataByCredentialRecordId( + credentialDetails + ); const finalResponse: IResponse = { statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.getProofPresentation, - data: getProofDetails + message: ResponseMessages.cloudWallet.success.credentialByRecordId, + data: credentialsDetailResponse }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Receive invitation by URL - * @param receiveInvitation - * @param res - * @returns Response from agent - */ - @Post('/receive-invitation-url') - @ApiOperation({ - summary: 'Receive Invitation by URL', - description: 'Endpoint to receive an invitation using a URL for the cloud wallet.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Invitation received successfully', type: ApiResponseDto }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async receiveInvitationByUrl( - @Res() res: Response, - @Body() receiveInvitation: ReceiveInvitationUrlDTO, - @User() user: user - ): Promise { - const { email, id } = user; - receiveInvitation.email = email; - receiveInvitation.userId = id; - const receiveInvitationData = await this.cloudWalletService.receiveInvitationByUrl(receiveInvitation); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.receive, - data: receiveInvitationData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Receive invitation by URL + * @param receiveInvitation + * @param res + * @returns Response from agent + */ + @Post('/receive-invitation-url') + @ApiOperation({ summary: 'Receive inviation using URL', description: 'Receive inviation using URL' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async receiveInvitationByUrl( + @Res() res: Response, + @Body() receiveInvitation: ReceiveInvitationUrlDTO, + @User() user: user + ): Promise { + const {email, id} = user; + receiveInvitation.email = email; + receiveInvitation.userId = id; + const receiveInvitationData = await this.cloudWalletService.receiveInvitationByUrl(receiveInvitation); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.receive, + data: receiveInvitationData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } - /** - * Accept offer - * @param acceptOffer - * @param res - * @returns Response from agent - */ - @Post('/accept-offer') - @ApiOperation({ - summary: 'Accept Credential Offer', - description: 'Endpoint to accept a credential offer for the cloud wallet.' - }) - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Credential offer accepted successfully', - type: ApiResponseDto - }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async acceptOffer(@Res() res: Response, @Body() acceptOffer: AcceptOfferDto, @User() user: user): Promise { - const { email, id } = user; - acceptOffer.email = email; - acceptOffer.userId = id; - const receiveInvitationData = await this.cloudWalletService.acceptOffer(acceptOffer); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.receive, - data: receiveInvitationData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Accept offer + * @param acceptOffer + * @param res + * @returns Response from agent + */ + @Post('/accept-offer') + @ApiOperation({ summary: 'Accept credential offer', description: 'Accept credential offer' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async acceptOffer( + @Res() res: Response, + @Body() acceptOffer: AcceptOfferDto, + @User() user: user + ): Promise { + const {email, id} = user; + acceptOffer.email = email; + acceptOffer.userId = id; + const receiveInvitationData = await this.cloudWalletService.acceptOffer(acceptOffer); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.receive, + data: receiveInvitationData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } /** * Create did - * @param createDidDto - * @param res + * @param orgId * @returns did */ @Post('/did') @ApiOperation({ - summary: 'Create DID', - description: 'Endpoint to create a new DID (Decentralized Identifier) for the cloud wallet.' + summary: 'Create new did', + description: 'Create new did for cloud wallet' }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'DID created successfully', type: ApiResponseDto }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), UserRoleGuard) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) async createDid( @Body() createDidDto: CreateCloudWalletDidDto, @User() user: user, @Res() res: Response ): Promise { - Validator.validateDid(createDidDto); - const { email, id } = user; + await validateDid(createDidDto); + const {email, id} = user; createDidDto.email = email; createDidDto.userId = id; if (createDidDto.seed && CommonConstants.SEED_LENGTH !== createDidDto.seed.length) { @@ -343,278 +531,515 @@ export class CloudWalletController { return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Get DID list by organization id - * @param res - * @returns DID list + + /** + * Create did + * @param orgId + * @returns did */ - @Get('/did') + @Post('/export-wallet') @ApiOperation({ - summary: 'Get DID List', - description: 'Endpoint to retrieve the list of DIDs associated with the cloud wallet.' + summary: 'Export Wallet', + description: 'Export Wallet' }) - @ApiResponse({ status: HttpStatus.OK, description: 'DID list retrieved successfully', type: ApiResponseDto }) + @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getDidList(@Res() res: Response, @User() user: user): Promise { - const { id, email } = user; + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async exportWallet( + @Body() exportWallet: ExportCloudWalletDto, + @User() user: user, + @Res() res: Response + ): Promise { + const {email, id} = user; + exportWallet.email = email; + exportWallet.userId = id; - const walletDetails: IWalletDetailsForDidList = { - userId: id, - email - }; + const exportWalletDetails = await this.cloudWalletService.exportWallet(exportWallet); - const didListDetails = await this.cloudWalletService.getDidList(walletDetails); const finalResponse: IResponse = { statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.didList, - data: didListDetails + message: ResponseMessages.agent.success.exportWallet, + data: exportWalletDetails }; - return res.status(HttpStatus.OK).json(finalResponse); - } - /** - * Create connection invitation - * @param createConnection - * @param res - * @returns success message - */ - @Post('/connections/invitation') - @ApiOperation({ - summary: 'Create Connection Invitation', - description: 'Endpoint to create a connection invitation for the cloud wallet.' - }) - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Connection invitation created successfully', - type: ApiResponseDto - }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async createConnection( - @Res() res: Response, - @Body() createConnection: CreateConnectionDto, - @User() user: user - ): Promise { - const { id, email } = user; - createConnection.userId = id; - createConnection.email = email; - - const createConnectionDetails = await this.cloudWalletService.createConnection(createConnection); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.createConnection, - data: createConnectionDetails - }; return res.status(HttpStatus.CREATED).json(finalResponse); } + - /** - * Get connection by connection id - * @param connectionId - * @param res - * @returns connection details - */ - @Get('/connection/:connectionId') - @ApiOperation({ - summary: 'Get Connection by ID', - description: 'Endpoint to retrieve connection details by connection ID.' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Connection details retrieved successfully', - type: ApiResponseDto - }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getconnectionById( - @Param('connectionId') connectionId: string, - @Res() res: Response, - @User() user: user - ): Promise { - const { id, email } = user; + /** + * Get DID list by tenant id + * @param tenantId + * @param res + * @returns DID list + */ + @Get('/did/:isDefault') + @ApiOperation({ summary: 'Get DID list from wallet', description: 'Get DID list from wallet' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getDidList( + @Res() res: Response, + @User() user: user, + @Param('isDefault') isDefault: boolean = false + ): Promise { + const { id, email } = user; - const connectionDetails: IConnectionDetailsById = { - userId: id, - email, - connectionId - }; + const walletDetails: IWalletDetailsForDidList = { + userId: id, + email, + isDefault + }; - const connectionDetailResponse = await this.cloudWalletService.getconnectionById(connectionDetails); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.connectionById, - data: connectionDetailResponse - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + const didListDetails = await this.cloudWalletService.getDidList(walletDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.didList, + data: didListDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get all wallet connections - * @param connectionListQueryOptions - * @param res - * @returns connection list - */ - @Get('/connections') - @ApiOperation({ - summary: 'Get All Wallet Connections', - description: 'Endpoint to retrieve all connections associated with the cloud wallet.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Connections retrieved successfully', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getAllconnectionById( - @Query() connectionListQueryOptions: GetAllCloudWalletConnectionsDto, - @Res() res: Response, - @User() user: user - ): Promise { - const { id, email } = user; + /** + * Accept proof request + * @param CreateConnectionDto + * @returns success message + */ + @Post('/connections/invitation') + @ApiOperation({ summary: 'Create connection invitation for cloud wallet', description: 'Create connection invitation' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async createConnection( + @Res() res: Response, + @Body() createConnection: CreateConnectionDto, + @User() user: user + ): Promise { + const { id, email } = user; + createConnection.userId = id; + createConnection.email = email; - connectionListQueryOptions.userId = id; - connectionListQueryOptions.email = email; + const createConnectionDetails = await this.cloudWalletService.createConnection(createConnection); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.createConnection, + data: createConnectionDetails + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const connectionDetailResponse = await this.cloudWalletService.getAllconnectionById(connectionListQueryOptions); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.connectionList, - data: connectionDetailResponse - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * Get connection list by tenant id and connection id + * @param tenantId + * @param connectionId + * @param res + * @returns DID list + */ + @Get('/connection/:connectionId') + @ApiOperation({ summary: 'Get connection by connection Id', description: 'Get connection by connection Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getconnectionById( + @Param('connectionId') connectionId: string, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; - /** - * Get credential list by tenant id - * @param credentialListQueryOptions - * @param res - * @returns Credential list - */ - @Get('/credential') - @ApiOperation({ - summary: 'Get Credential List', - description: 'Endpoint to retrieve the list of credentials associated with the cloud wallet.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Credential list retrieved successfully', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getCredentialList( - @Query() credentialListQueryOptions: CredentialListDto, - @Res() res: Response, - @User() user: user - ): Promise { - const { id, email } = user; + const connectionDetails: IConnectionDetailsById = { + userId: id, + email, + connectionId + }; - credentialListQueryOptions.userId = id; - credentialListQueryOptions.email = email; + const connectionDetailResponse = await this.cloudWalletService.getconnectionById(connectionDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.connectionById, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - const connectionDetailResponse = await this.cloudWalletService.getCredentialList(credentialListQueryOptions); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.credentials, - data: connectionDetailResponse - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * Get connection list by tenant id and connection id + * @param tenantId + * @param connectionId + * @param res + * @returns DID list + */ + @Post('/add-connection-type/:connectionId') + @ApiOperation({ summary: 'Add connection type', description: 'Add connection type' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async addConnectionTypeById( + @Param('connectionId') connectionId: string, + @Body() addConnectionType: AddConnectionTypeDto, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const connectionDetails: IAddConnectionType = { + userId: id, + email, + connectionId, + ... addConnectionType + }; + + const connectionDetailResponse = await this.cloudWalletService.addConnectionTypeById(connectionDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.addConnectionTypeById, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get connection list by tenant id + * @param res + * @returns DID list + */ + @Get('/connections') + @ApiOperation({ summary: 'Get all wallet connections', description: 'Get all wallet connections' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getAllconnectionById( + @Query() connectionListQueryOptions: GetAllCloudWalletConnectionsDto, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + connectionListQueryOptions.userId = id; + connectionListQueryOptions.email = email; + + const connectionDetailResponse = await this.cloudWalletService.getAllconnectionById(connectionListQueryOptions); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.connectionList, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Create self-attested credential + * @param SelfAttestedCredentialDto + * @returns success message + */ + @Post('/credentials/w3c/self-attested') + @ApiOperation({ summary: 'Create self-attested W3C credential for cloud wallet', description: 'Create self-attested W3C credential for cloud wallet' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async createSelfAttestedW3cCredential( + @Res() res: Response, + @Body() selfAttestedCredentialDto: SelfAttestedCredentialDto, + @User() user: user + ): Promise { + const { id, email } = user; + selfAttestedCredentialDto.userId = id; + selfAttestedCredentialDto.email = email; + + const selfAttestedCredential = await this.cloudWalletService.createSelfAttestedW3cCredential(selfAttestedCredentialDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.createSelfAttestedW3cCredential, + data: selfAttestedCredential + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * Get credential list by tenant id + * @param credentialListQueryOptions + * @param res + * @returns Credential list + */ + @Get('/credential') + @ApiOperation({ summary: 'Get credential list from cloud wallet', description: 'Get credential list from cloud wallet' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getCredentialList( + @Query() credentialListQueryOptions: CredentialListDto, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + credentialListQueryOptions.userId = id; + credentialListQueryOptions.email = email; + + const connectionDetailResponse = await this.cloudWalletService.getCredentialList(credentialListQueryOptions); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.credentials, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get W3C credential list by tenant id + * @param res + * @returns Credential list + */ + @Get('/credentials/w3c') + @ApiOperation({ summary: 'Get W3C credential list for cloud wallet', description: 'Get W3C credential list for cloud wallet' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getAllW3cCredentials( + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const credentialDetail: IW3cCredentials = { + userId: id, + email + }; + + const w3cCredentials = await this.cloudWalletService.getAllW3cCredentials(credentialDetail); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.credentials, + data: w3cCredentials + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get credential list by tenant id + * @param credentialListQueryOptions + * @param res + * @returns Credential list + */ + @Get('/credential/:credentialRecordId') + @ApiOperation({ summary: 'Get credential by credential record Id', description: 'Get credential by credential record Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getCredentialByCredentialRecordId( + @Param('credentialRecordId') credentialRecordId: string, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const credentialDetails: ICredentialDetails = { + userId: id, + email, + credentialRecordId + }; + + const connectionDetailResponse = await this.cloudWalletService.getCredentialByCredentialRecordId(credentialDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.credentialByRecordId, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get W3C credential by Record Id + * @param credentialListQueryOptions + * @param res + * @returns Credential Detail + */ + @Get('/credential/w3c/:credentialRecordId') + @ApiOperation({ summary: 'Get credential by credential record Id', description: 'Get credential by credential record Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getW3cCredentialByCredentialRecordId( + @Param('credentialRecordId') credentialRecordId: string, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const credentialDetails: IW3cCredentials = { + userId: id, + email, + credentialRecordId + }; + + const w3cCredential = await this.cloudWalletService.getW3cCredentialByCredentialRecordId(credentialDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.credentialByRecordId, + data: w3cCredential + }; + return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Get credential by credential record id - * @param credentialRecordId + * Get credential Format data by credential id + * @param credentialListQueryOptions * @param res - * @returns Credential details + * @returns Credential list */ - @Get('/credential/:credentialRecordId') + @Get('/proof-formdata/:proofRecordId') @ApiOperation({ - summary: 'Get Credential by Record ID', - description: 'Endpoint to retrieve credential details by credential record ID.' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Credential details retrieved successfully', - type: ApiResponseDto + summary: 'Get proof presentation by record Id', + description: 'Get proof presentation by record Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getCredentialByCredentialRecordId( - @Param('credentialRecordId') credentialRecordId: string, + async getProofFormatDataByProofRecordId( + @Param('proofRecordId') proofRecordId: string, @Res() res: Response, @User() user: user ): Promise { const { id, email } = user; - const credentialDetails: ICredentialDetails = { + const proofPresentationDetails: IProofPresentationDetails = { userId: id, email, - credentialRecordId + proofRecordId }; - const connectionDetailResponse = await this.cloudWalletService.getCredentialByCredentialRecordId(credentialDetails); + const proofDetailResponse = await this.cloudWalletService.getProofFormatDataByProofRecordId( + proofPresentationDetails + ); const finalResponse: IResponse = { statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.credentialByRecordId, - data: connectionDetailResponse + message: ResponseMessages.cloudWallet.success.proofPresentationByRecordId, + data: proofDetailResponse }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get basic message by connection id - * @param connectionId +/** + * Delete credential by credential id + * @param credentialListQueryOptions * @param res - * @returns Basic message details + * @returns deleted credential */ - @Get('/basic-message/:connectionId') - @ApiOperation({ - summary: 'Get Basic Message by Connection ID', - description: 'Endpoint to retrieve basic message details by connection ID.' - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Basic message details retrieved successfully', - type: ApiResponseDto - }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async getBasicMessageByConnectionId( - @Param('connectionId') connectionId: string, - @Res() res: Response, - @User() user: user - ): Promise { - const { id, email } = user; +@Delete('/credential/:credentialRecordId') +@ApiOperation({ + summary: 'Get credential by credential record Id', + description: 'Get credential by credential record Id' +}) +@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) +@UseGuards(AuthGuard('jwt'), UserRoleGuard) +async deleteCredentialByCredentialRecordId( + @Param('credentialRecordId') credentialRecordId: string, + @Res() res: Response, + @User() user: user +): Promise { + const { id, email } = user; - const connectionDetails: IBasicMessage = { - userId: id, - email, - connectionId - }; + const credentialDetails: ICredentialDetails = { + userId: id, + email, + credentialRecordId + }; - const basicMessageDetailResponse = await this.cloudWalletService.getBasicMessageByConnectionId(connectionDetails); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.cloudWallet.success.basicMessageByConnectionId, - data: basicMessageDetailResponse - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + const connectionDetailResponse = await this.cloudWalletService.deleteCredentialByCredentialRecordId( + credentialDetails + ); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.deleteCredential, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); +} - /** - * Send basic message - * @param connectionId - * @param messageDetails +/** + * Delete W3C credential by credential id + * @param credentialListQueryOptions * @param res - * @returns success message + * @returns deleted W3C credential */ - @Post('/basic-message/:connectionId') - @ApiOperation({ summary: 'Send Basic Message', description: 'Endpoint to send a basic message to a connection.' }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Basic message sent successfully', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), UserRoleGuard) - async sendBasicMessage( - @Param('connectionId') connectionId: string, - @Res() res: Response, - @Body() messageDetails: BasicMessageDTO, - @User() user: user - ): Promise { - const { id, email } = user; - messageDetails.userId = id; - messageDetails.email = email; - messageDetails.connectionId = connectionId; - const basicMessageDetails = await this.cloudWalletService.sendBasicMessage(messageDetails); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.cloudWallet.success.basicMessage, - data: basicMessageDetails - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } +@Delete('/credential/w3c/:credentialRecordId') +@ApiOperation({ + summary: 'Get credential by credential record Id', + description: 'Get credential by credential record Id' +}) +@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) +@UseGuards(AuthGuard('jwt'), UserRoleGuard) +async deleteW3cCredentialByCredentialRecordId( + @Param('credentialRecordId') credentialRecordId: string, + @Res() res: Response, + @User() user: user +): Promise { + const { id, email } = user; + + const credentialDetails: ICredentialDetails = { + userId: id, + email, + credentialRecordId + }; + + const connectionDetailResponse = await this.cloudWalletService.deleteW3cCredentialByCredentialRecordId( + credentialDetails + ); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.deleteCredential, + data: connectionDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); +} + + /** + * Get basic-message by connection id + * @param connectionId + * @param res + * @returns Credential list + */ + @Get('/basic-message/:connectionId') + @ApiOperation({ summary: 'Get basic message by connection id', description: 'Get basic message by connection id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async getBasicMessageByConnectionId( + @Param('connectionId') connectionId: string, + @Res() res: Response, + @User() user: user + ): Promise { + const { id, email } = user; + + const connectionDetails: IBasicMessage = { + userId: id, + email, + connectionId + }; + + const basicMessageDetailResponse = await this.cloudWalletService.getBasicMessageByConnectionId(connectionDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.cloudWallet.success.basicMessageByConnectionId, + data: basicMessageDetailResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Get basic-message by connection id + * @param credentialListQueryOptions + * @param res + * @returns Credential list + */ + @Post('/basic-message/:connectionId') + @ApiOperation({ summary: 'send question', description: 'send question' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), UserRoleGuard) + async sendBasicMessage( + @Param('connectionId') connectionId: string, + @Res() res: Response, + @Body() messageDetails: BasicMessageDTO, + @User() user: user + ): Promise { + const { id, email } = user; + messageDetails.userId = id; + messageDetails.email = email; + messageDetails.connectionId = connectionId; + const basicMessageDetails = await this.cloudWalletService.sendBasicMessage(messageDetails); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.cloudWallet.success.basicMessage, + data: basicMessageDetails + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + } diff --git a/apps/api-gateway/src/cloud-wallet/cloud-wallet.module.ts b/apps/api-gateway/src/cloud-wallet/cloud-wallet.module.ts index fb43a467d..480b0bdf8 100644 --- a/apps/api-gateway/src/cloud-wallet/cloud-wallet.module.ts +++ b/apps/api-gateway/src/cloud-wallet/cloud-wallet.module.ts @@ -4,7 +4,6 @@ import { CloudWalletService } from './cloud-wallet.service'; import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -18,7 +17,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [CloudWalletController], - providers: [CloudWalletService, NATSClient] + providers: [CloudWalletService] }) export class CloudWalletModule { diff --git a/apps/api-gateway/src/cloud-wallet/cloud-wallet.service.ts b/apps/api-gateway/src/cloud-wallet/cloud-wallet.service.ts index 6400aa046..17de8d9c4 100644 --- a/apps/api-gateway/src/cloud-wallet/cloud-wallet.service.ts +++ b/apps/api-gateway/src/cloud-wallet/cloud-wallet.service.ts @@ -1,139 +1,204 @@ -import { - IAcceptOffer, - ICreateCloudWallet, - ICreateCloudWalletDid, - IReceiveInvitation, - IAcceptProofRequest, - IProofRequestRes, - ICloudBaseWalletConfigure, - IGetProofPresentation, - IGetProofPresentationById, - IGetStoredWalletInfo, - IStoredWalletDetails, - IWalletDetailsForDidList, - IConnectionDetailsById, - ITenantDetail, - ICredentialDetails, - ICreateConnection, - IConnectionInvitationResponse, - GetAllCloudWalletConnections, - IBasicMessage, - IBasicMessageDetails -} from '@credebl/common/interfaces/cloud-wallet.interface'; -import { Inject, Injectable } from '@nestjs/common'; -import { BaseService } from 'libs/service/base.service'; -import { NATSClient } from '@credebl/common/NATSClient'; + +import { IAcceptOffer, ICreateCloudWallet, ICreateCloudWalletDid, IReceiveInvitation, IAcceptProofRequest, IProofRequestRes, ICloudBaseWalletConfigure, IGetProofPresentation, IGetProofPresentationById, IGetStoredWalletInfo, IStoredWalletDetails, IWalletDetailsForDidList, IConnectionDetailsById, ITenantDetail, ICredentialDetails, ICreateConnection, IConnectionInvitationResponse, GetAllCloudWalletConnections, IBasicMessage, IBasicMessageDetails, IProofPresentationDetails, IGetCredentialsForRequest, ICredentialForRequestRes, IProofPresentationPayloadWithCred, IDeclineProofRequest, BaseAgentInfo, IW3cCredentials, IDeleteCloudWallet, IExportCloudWallet, ICheckCloudWalletStatus, IAddConnectionType } from '@credebl/common/interfaces/cloud-wallet.interface'; +import { Inject, Injectable} from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; +// eslint-disable-next-line camelcase +import { cloud_wallet_user_info, user } from '@prisma/client'; +// import { Prisma } from '@prisma/client'; +import { BaseService } from 'libs/service/base.service'; +import { UpdateBaseWalletDto } from './dtos/cloudWallet.dto'; +import { SelfAttestedCredentialDto } from './dtos/self-attested-credential.dto'; @Injectable() export class CloudWalletService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly cloudWalletServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly cloudWalletServiceProxy: ClientProxy) { super('CloudWalletServiceProxy'); } - configureBaseWallet(cloudBaseWalletConfigure: ICloudBaseWalletConfigure): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'configure-cloud-base-wallet', - cloudBaseWalletConfigure - ); + async configureBaseWallet( + cloudBaseWalletConfigure: ICloudBaseWalletConfigure + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'configure-cloud-base-wallet', cloudBaseWalletConfigure); } - createConnection(createConnection: ICreateConnection): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'create-connection-by-holder', - createConnection - ); + checkCloudWalletStatus( + acceptProofRequest: ICheckCloudWalletStatus + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'check-cloud-wallet-status', acceptProofRequest); } - acceptProofRequest(acceptProofRequest: IAcceptProofRequest): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'accept-proof-request-by-holder', - acceptProofRequest - ); + createConnection( + createConnection: ICreateConnection + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'create-connection-by-holder', createConnection); } - getProofById(proofPresentationByIdPayload: IGetProofPresentationById): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'get-proof-by-proof-id-holder', - proofPresentationByIdPayload - ); + acceptProofRequest( + acceptProofRequest: IAcceptProofRequest + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'accept-proof-request-by-holder', acceptProofRequest); } - getProofPresentation(proofPresentationPayload: IGetProofPresentation): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'get-proof-presentation-holder', - proofPresentationPayload - ); + declineProofRequest( + acceptProofRequest: IDeclineProofRequest + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'decline-proof-request-by-holder', acceptProofRequest); } - createCloudWallet(cloudWalletDetails: ICreateCloudWallet): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'create-cloud-wallet', cloudWalletDetails); + getProofById( + proofPresentationByIdPayload: IGetProofPresentationById + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-proof-by-proof-id-holder', proofPresentationByIdPayload); } - receiveInvitationByUrl(ReceiveInvitationDetails: IReceiveInvitation): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'receive-invitation-by-url', - ReceiveInvitationDetails - ); + submitProofWithCred( + proofPresentationByIdPayload: IProofPresentationPayloadWithCred + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'submit-proof-with-cred', proofPresentationByIdPayload); } - - acceptOffer(acceptOfferDetails: IAcceptOffer): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'accept-credential-offer', acceptOfferDetails); + getCredentialsForRequest( + proofPresentationByIdPayload: IGetCredentialsForRequest + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-credentials-for-request', proofPresentationByIdPayload); + } + getProofPresentation( + proofPresentationPayload: IGetProofPresentation + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-proof-presentation-holder', proofPresentationPayload); } - createDid(createDidDetails: ICreateCloudWalletDid): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'create-cloud-wallet-did', createDidDetails); + createCloudWallet( + cloudWalletDetails: ICreateCloudWallet + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'create-cloud-wallet', cloudWalletDetails); } - getDidList(walletDetails: IWalletDetailsForDidList): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'cloud-wallet-did-list', walletDetails); + async deleteCloudWallet( + cloudWalletDetails: IDeleteCloudWallet + // eslint-disable-next-line camelcase + ): Promise { + // eslint-disable-next-line camelcase + const res: cloud_wallet_user_info = await this.sendNatsMessage(this.cloudWalletServiceProxy, 'delete-cloud-wallet', cloudWalletDetails); + if (cloudWalletDetails.deleteHolder) { + await this.sendNatsMessage(this.cloudWalletServiceProxy, 'delete-user', res.userId); + } + return res; } - getconnectionById(connectionDetails: IConnectionDetailsById): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'get-cloud-wallet-connection-by-id', - connectionDetails - ); + + getBaseWalletDetails(user: user): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-base-wallet-details', user); } - getAllconnectionById(connectionDetails: GetAllCloudWalletConnections): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'get-all-cloud-wallet-connections-list-by-id', - connectionDetails - ); + + updateBaseWalletDetails(updateBaseWalletDto: UpdateBaseWalletDto): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'update-base-wallet-details', updateBaseWalletDto); } - getCredentialList(tenantDetails: ITenantDetail): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'wallet-credential-by-id', tenantDetails); + receiveInvitationByUrl( + ReceiveInvitationDetails: IReceiveInvitation + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'receive-invitation-by-url', ReceiveInvitationDetails); } - getCredentialByCredentialRecordId(credentialDetails: ICredentialDetails): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'wallet-credential-by-record-id', - credentialDetails - ); + acceptOffer( + acceptOfferDetails: IAcceptOffer + ): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'accept-credential-offer', acceptOfferDetails); } - getBasicMessageByConnectionId(connectionDetails: IBasicMessage): Promise { - return this.natsClient.sendNatsMessage( - this.cloudWalletServiceProxy, - 'basic-message-list-by-connection-id', - connectionDetails - ); + createDid(createDidDetails: ICreateCloudWalletDid): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'create-cloud-wallet-did', createDidDetails); } - sendBasicMessage(messageDetails: IBasicMessageDetails): Promise { - return this.natsClient.sendNatsMessage(this.cloudWalletServiceProxy, 'send-basic-message', messageDetails); + exportWallet(exportWallet: IExportCloudWallet): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'export-cloud-wallet', exportWallet); } + +getDidList( + walletDetails: IWalletDetailsForDidList +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'cloud-wallet-did-list', walletDetails); +} + +getconnectionById( + connectionDetails: IConnectionDetailsById +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-cloud-wallet-connection-by-id', connectionDetails); +} + +addConnectionTypeById( + connectionDetails: IAddConnectionType +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'Add-cloud-wallet-connection--type-by-id', connectionDetails); +} + +getAllconnectionById( + connectionDetails: GetAllCloudWalletConnections +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-all-cloud-wallet-connections-list-by-id', connectionDetails); +} + +getCredentialList( + tenantDetails: ITenantDetail +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'wallet-credential-by-id', tenantDetails); +} + +getAllW3cCredentials( + w3cCredentials: IW3cCredentials +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-all-w3c-credenentials', w3cCredentials); +} + +getW3cCredentialByCredentialRecordId( + w3CcredentialDetail: IW3cCredentials +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'get-w3c-credential-by-record-id', w3CcredentialDetail); +} + +getCredentialByCredentialRecordId( + credentialDetails: ICredentialDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'wallet-credential-by-record-id', credentialDetails); +} + +getCredentialFormatDataByCredentialRecordId( + credentialDetails: ICredentialDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'wallet-credentialFormatData-by-record-id', credentialDetails); +} + +getProofFormatDataByProofRecordId( + credentialDetails: IProofPresentationDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'wallet-Proof-presentation-FormatData-by-record-id', credentialDetails); +} + +deleteCredentialByCredentialRecordId( + credentialDetails: ICredentialDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'delete-credential-by-record-id', credentialDetails); +} + +deleteW3cCredentialByCredentialRecordId( + credentialDetails: ICredentialDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'delete-w3c-credential-by-record-id', credentialDetails); +} + +getBasicMessageByConnectionId( + connectionDetails: IBasicMessage +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'basic-message-list-by-connection-id', connectionDetails); +} + +sendBasicMessage( + messageDetails: IBasicMessageDetails +): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'send-basic-message', messageDetails); +} + +createSelfAttestedW3cCredential(selfAttestedCredentialDto: SelfAttestedCredentialDto): Promise { + return this.sendNatsMessage(this.cloudWalletServiceProxy, 'create-self-attested-w3c-credential', selfAttestedCredentialDto); +} } diff --git a/apps/api-gateway/src/cloud-wallet/dtos/accept-proof-request-with-cred.dto.ts b/apps/api-gateway/src/cloud-wallet/dtos/accept-proof-request-with-cred.dto.ts new file mode 100644 index 000000000..bcf9ac91b --- /dev/null +++ b/apps/api-gateway/src/cloud-wallet/dtos/accept-proof-request-with-cred.dto.ts @@ -0,0 +1,32 @@ +import { IsObject, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +class PresentationExchangeDto { + @ApiProperty({ type: 'object', additionalProperties: { type: 'number' } }) + @IsObject() + credentials: Record; +} + +class ProofFormatsDto { + @ApiProperty({ type: PresentationExchangeDto }) + @ValidateNested() + @Type(() => PresentationExchangeDto) + presentationExchange: PresentationExchangeDto; +} + +export class ProofWithCredDto { + @ApiProperty({ type: ProofFormatsDto }) + @ValidateNested() + @Type(() => ProofFormatsDto) + proofFormats: ProofFormatsDto; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + comment?: string; + + @ApiProperty() + @IsString() + proofRecordId: string; +} diff --git a/apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto.ts b/apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto.ts index 4dde297c5..5abe6eeda 100644 --- a/apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto.ts +++ b/apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString, Matches, MaxLength } from 'class-validator'; +import { IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, Min } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; @@ -53,19 +53,16 @@ export class ReceiveInvitationUrlDTO { @ApiPropertyOptional() @IsBoolean({ message: 'autoAcceptConnection must be a boolean' }) - @Transform(({ value }) => trim(value)) @IsOptional() autoAcceptConnection?: boolean; @ApiPropertyOptional() @IsBoolean({ message: 'autoAcceptInvitation must be a boolean' }) - @Transform(({ value }) => trim(value)) @IsOptional() autoAcceptInvitation?: boolean; @ApiPropertyOptional() @IsBoolean({ message: 'reuseConnection must be a boolean' }) - @Transform(({ value }) => trim(value)) @IsOptional() reuseConnection?: boolean; @@ -90,13 +87,21 @@ export class ReceiveInvitationUrlDTO { @IsNotSQLInjection({ message: 'invitationUrl is required.' }) invitationUrl: string; + @ApiPropertyOptional() + @IsString({ message: 'connectionType must be a string' }) + @IsOptional() + @IsNotEmpty({ message: 'please provide valid connectionType' }) + @Transform(({ value }) => trim(value)) + @IsNotSQLInjection({ message: 'connectionType is required.' }) + connectionType?: string; + email?: string; userId?: string; } export class AcceptOfferDto { - @ApiPropertyOptional({ example: 'always', description: 'autoAcceptCredential', enum: ['always', 'contentApproved', 'never'] }) + @ApiPropertyOptional({ example: 'string', description: 'autoAcceptCredential' }) @Transform(({ value }) => trim(value)) @IsString({ message: 'autoAcceptCredential must be a string' }) autoAcceptCredential: string; @@ -163,7 +168,7 @@ export class ReceiveInvitationUrlDTO { @IsString({ message: 'role must be in string format.' }) role?: string; - @ApiPropertyOptional({example: '651727dab6dfdbb4f18afff5f368d13b0dca41fd26bd5e1c7953457524d645e6'}) + @ApiPropertyOptional({example: ''}) @IsOptional() @IsString({ message: 'private key must be in string format.' }) @Transform(({ value }) => trim(value)) @@ -184,13 +189,39 @@ export class ReceiveInvitationUrlDTO { @IsOptional() @Transform(({ value }) => trim(value)) @IsString({ message: 'endorser did must be in string format.' }) - endorserDid?: string; + endorserDid?: string; + + @ApiPropertyOptional({example: 'false'}) + @IsOptional() + @IsBoolean({ message: 'isDefault must be boolean value.' }) + isDefault?: boolean = false; email?: string; userId?: string; } + export class ExportCloudWalletDto { + + + @ApiPropertyOptional({ example: 'XzFjo1RTZ2h9UVFCnPUyaQ' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'passKey is required' }) + @IsString({ message: 'passKey must be in string format.' }) + passKey: string; + + @ApiPropertyOptional({ example: 'walletID' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'walletID is required' }) + @IsString({ message: 'walletID must be in string format.' }) + walletID: string; + + + email: string; + + userId: string; +} + export class CredentialListDto { @ApiProperty({ required: false}) @IsNotEmpty() @@ -263,4 +294,33 @@ export class BasicMessageDTO { userId?: string; connectionId: string; +} + +export class UpdateBaseWalletDto { + + @ApiProperty({ + example: 5, + description: 'Maximum number of sub wallets allowed' + }) + @IsInt() + @Min(1) + maxSubWallets: number; + + @ApiPropertyOptional({ default: true }) + @IsBoolean({ message: 'isActive must be a boolean' }) + @IsOptional() + isActive: boolean = true; + + email?: string; + userId?: string; + walletId: string; + +} + +export class AddConnectionTypeDto { + @ApiProperty({ example: 'type'}) + @IsNotEmpty({ message: 'connectionType is required' }) + @Transform(({ value }) => trim(value)) + @IsString({ message: 'connectionType should be in string format.' }) + connectionType: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/cloud-wallet/dtos/configure-base-wallet.dto.ts b/apps/api-gateway/src/cloud-wallet/dtos/configure-base-wallet.dto.ts index 7c14c928a..be423070b 100644 --- a/apps/api-gateway/src/cloud-wallet/dtos/configure-base-wallet.dto.ts +++ b/apps/api-gateway/src/cloud-wallet/dtos/configure-base-wallet.dto.ts @@ -1,7 +1,8 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { IsHostPortOrDomain } from '@credebl/common/cast.helper'; +// import { Transform } from 'class-transformer'; export class CloudBaseWalletConfigureDto { @ApiProperty({ example: 'xxx-xxxx-xxxx' }) @@ -20,6 +21,26 @@ export class CloudBaseWalletConfigureDto { @IsHostPortOrDomain({ message: 'Agent Endpoint must be a valid protocol://host:port or domain'}) agentEndpoint: string; + @ApiProperty({ + example: 5, + description: 'Maximum number of sub wallets allowed' + }) + @IsInt() + @Min(1) + maxSubWallets: number; + + // @ApiProperty({ example: '5edee49e-17f1-4b54-9070-ef00789777d4' }) + // @IsString({ message: 'orgId must be a string' }) + // @IsNotEmpty({ message: 'please provide valid orgId' }) + // orgId: string; + + // @ApiProperty() + // @Transform(({ value }) => trim(value)) + // @IsNotEmpty({ message: 'webhookUrl is required.' }) + // @IsString({ message: 'webhookUrl must be in string format.' }) + // @IsUrl(undefined, {message:'webhookUrl is not valid'}) + // webhookUrl: string; + userId: string; email: string; diff --git a/apps/api-gateway/src/cloud-wallet/dtos/decline-proof-request.dto.ts b/apps/api-gateway/src/cloud-wallet/dtos/decline-proof-request.dto.ts new file mode 100644 index 000000000..4231007e7 --- /dev/null +++ b/apps/api-gateway/src/cloud-wallet/dtos/decline-proof-request.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; + +export class DeclineProofRequestDto { + @ApiProperty({ example: '4e687079-273b-447b-b9dd-9589c84dc6dd' }) + @IsString({ message: 'proofRecordId must be a string' }) + @IsNotEmpty({ message: 'please provide valid proofRecordId' }) + @IsUUID() + proofRecordId: string; + + @ApiPropertyOptional({ example: false }) + @IsOptional() + @IsBoolean({ message: 'sendProblemReport must be a boolean' }) + sendProblemReport?: boolean; + + @ApiPropertyOptional({ example: '' }) + @IsOptional() + @IsString({ message: 'problemReportDescription must be a string' }) + problemReportDescription?: string; + + userId: string; + + email: string; +} diff --git a/apps/api-gateway/src/cloud-wallet/dtos/self-attested-credential.dto.ts b/apps/api-gateway/src/cloud-wallet/dtos/self-attested-credential.dto.ts new file mode 100644 index 000000000..e1245c8cd --- /dev/null +++ b/apps/api-gateway/src/cloud-wallet/dtos/self-attested-credential.dto.ts @@ -0,0 +1,49 @@ +/* eslint-disable @typescript-eslint/array-type */ + +import { ApiProperty } from '@nestjs/swagger'; +import { + IsNotEmpty, + IsString} from 'class-validator'; +import { IsCredentialJsonLdContext, SingleOrArray } from '../../issuance/utils/helper'; +import { JsonObject } from '../../issuance/interfaces'; + + +export class SelfAttestedCredentialDto { + @ApiProperty({ + example: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://dev-schema.ngotag.com/schemas/50add817-e7f1-4651-bd62-5471b2f5918f' + ] + }) + @IsNotEmpty({ message: 'context is required' }) + @IsCredentialJsonLdContext() + '@context': Array; + + @ApiProperty({ + example: [ + 'VerifiableCredential', + 'Email' + ] + }) + @IsNotEmpty({ message: 'type is required' }) + type: string[]; + + @ApiProperty({ + example: { + 'Email': 'dorji@gmail.com' + } + }) + @IsNotEmpty({ message: ' credential subject required' }) + credentialSubject: SingleOrArray; + [key: string]: unknown; + + @ApiProperty({ + example: 'Ed25519Signature2018' + }) + @IsString() + @IsNotEmpty({ message: 'proof type is required' }) + proofType: string; + + userId: string; + email: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts b/apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts index a15929cf6..064433a16 100644 --- a/apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts +++ b/apps/api-gateway/src/cloud-wallet/enums/connections.enum.ts @@ -1,4 +1,4 @@ export declare enum HandshakeProtocol { - Connections = "https://didcomm.org/connections/1.0", - DidExchange = "https://didcomm.org/didexchange/1.0" + Connections = 'https://didcomm.org/connections/1.0', + DidExchange = 'https://didcomm.org/didexchange/1.0' } \ No newline at end of file diff --git a/apps/api-gateway/src/connection/connection.controller.ts b/apps/api-gateway/src/connection/connection.controller.ts index 6d3ba32d7..fdb95e716 100644 --- a/apps/api-gateway/src/connection/connection.controller.ts +++ b/apps/api-gateway/src/connection/connection.controller.ts @@ -1,43 +1,13 @@ import { IResponse } from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { - Controller, - Post, - Logger, - Body, - UseGuards, - HttpStatus, - Res, - Get, - Param, - UseFilters, - Query, - Inject, - ParseUUIDPipe, - BadRequestException, - Delete -} from '@nestjs/common'; +import { Controller, Post, Logger, Body, UseGuards, HttpStatus, Res, Get, Param, UseFilters, Query, Inject, ParseUUIDPipe, BadRequestException, Delete } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { - ApiBearerAuth, - ApiExcludeEndpoint, - ApiForbiddenResponse, - ApiOperation, - ApiQuery, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { User } from '../authz/decorators/user.decorator'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ConnectionService } from './connection.service'; -import { - ConnectionDto, - CreateOutOfBandConnectionInvitation, - ReceiveInvitationDto, - ReceiveInvitationUrlDto -} from './dtos/connection.dto'; +import { ConnectionDto, CreateOutOfBandConnectionInvitation, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; import { IUserRequestInterface } from './interfaces'; import { Response } from 'express'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; @@ -49,125 +19,108 @@ import { GetAllAgentConnectionsDto, GetAllConnectionsDto } from './dtos/get-all- import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; -import { BasicMessageDto, QuestionAnswerWebhookDto, QuestionDto } from './dtos/question-answer.dto'; +import { ClientProxy} from '@nestjs/microservices'; +import { BasicMessageDto, QuestionAnswerWebhookDto, QuestionDto} from './dtos/question-answer.dto'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; -import { TrimStringParamPipe } from '@credebl/common/cast.helper'; -import { ClientProxy } from '@nestjs/microservices'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('connections') @ApiBearerAuth() -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class ConnectionController { - private readonly logger = new Logger('Connection'); - constructor( - private readonly connectionService: ConnectionService, - @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy - ) {} - /** - * Get connection details by connectionId - * @param connectionId The ID of the connection - * @param orgId The ID of the organization - * @returns Connection details by connection Id - */ - @Get('orgs/:orgId/connections/:connectionId') - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiOperation({ - summary: `Get connection details by connectionId`, - description: `Retrieve the details of a specific connection by its connectionId for a specific organization.` - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async getConnectionsById( - @User() user: IUserRequest, - @Param('orgId') orgId: string, - @Param( - 'connectionId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.connection.error.invalidConnectionId); - } - }) - ) - connectionId: string, - @Res() res: Response - ): Promise { - const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetchConnection, - data: connectionsDetails - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + private readonly logger = new Logger('Connection'); + constructor(private readonly connectionService: ConnectionService, + @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy + ) { } - /** - * Get all connections - * @param user The user making the request - * @param orgId The ID of the organization - * @returns List of all connections for a specific organization - */ - @Get('/orgs/:orgId/connections') - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiOperation({ - summary: `Fetch all connections by orgId`, - description: `Retrieve all connections for a specific organization. Supports pagination and sorting.` - }) - @ApiQuery({ - name: 'sortField', - enum: SortFields, - required: false - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async getConnections( - @Query() getAllConnectionsDto: GetAllConnectionsDto, - @User() user: IUserRequest, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); - } - }) - ) - orgId: string, - @Res() res: Response - ): Promise { - const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllConnectionsDto; - const connectionSearchCriteria: IConnectionSearchCriteria = { - pageNumber, - searchByText, - pageSize, - sortField, - sortBy - }; - const connectionDetails = await this.connectionService.getConnections(connectionSearchCriteria, user, orgId); + /** + * Get connection details by connectionId + * @param connectionId + * @param orgId + * @returns connection details by connection Id + */ + @Get('orgs/:orgId/connections/:connectionId') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Get connections by connection Id`, + description: `Get connections by connection Id` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getConnectionsById( + @User() user: IUserRequest, + @Param('connectionId') connectionId: string, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + const connectionsDetails = await this.connectionService.getConnectionsById(user, connectionId, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.connection.success.fetchConnection, + data: connectionsDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.fetch, - data: connectionDetails - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * Description: Get all connections + * @param user + * @param orgId + * + */ + @Get('/orgs/:orgId/connections') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiOperation({ + summary: `Fetch all connections by orgId`, + description: `Fetch all connections by orgId` + }) + @ApiQuery({ + name: 'sortField', + enum: SortFields, + required: false + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getConnections( + @Query() getAllConnectionsDto: GetAllConnectionsDto, + @User() user: IUserRequest, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, + @Res() res: Response + ): Promise { - /** - * Get all connections from agent - * @param user The user making the request - * @param orgId The ID of the organization - * @returns List of all connections from agent for a specific organization + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllConnectionsDto; + const connectionSearchCriteria: IConnectionSearchCriteria = { + pageNumber, + searchByText, + pageSize, + sortField, + sortBy + }; + const connectionDetails = await this.connectionService.getConnections(connectionSearchCriteria, user, orgId); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.connection.success.fetch, + data: connectionDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * Description: Get all connections from agent + * @param user + * @param orgId + * */ @Get('/orgs/:orgId/agent/connections') @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @ApiOperation({ summary: `Fetch all connections from agent by orgId`, - description: `Retrieve all connections from agent for the organization.` + description: `Fetch all connections from agent by orgId` }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getConnectionListFromAgent( @@ -175,7 +128,11 @@ export class ConnectionController { @Param('orgId') orgId: string, @Res() res: Response ): Promise { - const connectionDetails = await this.connectionService.getConnectionListFromAgent(getAllConnectionsDto, orgId); + + const connectionDetails = await this.connectionService.getConnectionListFromAgent( + getAllConnectionsDto, + orgId + ); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -185,182 +142,135 @@ export class ConnectionController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get question-answer record - * @param orgId The ID of the organization - * @returns Question-answer record for a specific organization - */ - @Get('orgs/:orgId/question-answer/question') - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles( - OrgRoles.OWNER, - OrgRoles.ADMIN, - OrgRoles.ISSUER, - OrgRoles.VERIFIER, - OrgRoles.MEMBER, - OrgRoles.HOLDER, - OrgRoles.SUPER_ADMIN, - OrgRoles.PLATFORM_ADMIN - ) - @ApiOperation({ - summary: `Get question-answer record`, - description: `Retrieve the question-answer record for a specific organization.` - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async getQuestionAnswersRecord(@Param('orgId') orgId: string, @Res() res: Response): Promise { - const record = await this.connectionService.getQuestionAnswersRecord(orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.connection.success.questionAnswerRecord, - data: record - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - /** - * Create out-of-band connection invitation - * @param orgId The ID of the organization - * @param createOutOfBandConnectionInvitation The details of the out-of-band connection invitation - * @param reqUser The user making the request - * @param res The response object - * @returns Created out-of-band connection invitation URL - */ - @Post('/orgs/:orgId/connections') - @ApiOperation({ - summary: 'Create outbound out-of-band connection invitation', - description: 'Create an outbound out-of-band connection invitation for the organization.' - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async createConnectionInvitation( - @Param('orgId') orgId: string, - @Body() createOutOfBandConnectionInvitation: CreateOutOfBandConnectionInvitation, - @User() reqUser: IUserRequestInterface, - @Res() res: Response - ): Promise { - createOutOfBandConnectionInvitation.orgId = orgId; - const connectionData = await this.connectionService.createConnectionInvitation( - createOutOfBandConnectionInvitation, - reqUser - ); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.create, - data: connectionData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + @Get('orgs/:orgId/question-answer/question') + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) + @ApiOperation({ + summary: `Get question-answer record`, + description: `Get question-answer record` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getQuestionAnswersRecord( + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + const record = await this.connectionService.getQuestionAnswersRecord(orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.connection.success.questionAnswerRecord, + data: record + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Send question - * @param orgId The ID of the organization - * @param connectionId The ID of the connection - * @param questionDto The details of the question - * @param reqUser The user making the request - * @param res The response object - * @returns The details of the sent question - */ - @Post('/orgs/:orgId/question-answer/question/:connectionId') - @ApiOperation({ summary: 'Send question', description: 'Send a question to the connection ID' }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles( - OrgRoles.OWNER, - OrgRoles.ADMIN, - OrgRoles.ISSUER, - OrgRoles.VERIFIER, - OrgRoles.MEMBER, - OrgRoles.HOLDER, - OrgRoles.SUPER_ADMIN, - OrgRoles.PLATFORM_ADMIN - ) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async sendQuestion( - @Param('orgId') orgId: string, - @Param('connectionId', TrimStringParamPipe) connectionId: string, - @Body() questionDto: QuestionDto, - @User() reqUser: IUserRequestInterface, - @Res() res: Response - ): Promise { - questionDto.orgId = orgId; - questionDto.connectionId = connectionId; - const questionData = await this.connectionService.sendQuestion(questionDto); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.questionSend, - data: questionData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - /** - * Receive Invitation URL - * @param orgId The ID of the organization - * @param receiveInvitationUrl The details of the invitation URL - * @param user The user making the request - * @param res The response object - * @returns The details of the received invitation URL - */ - @Post('/orgs/:orgId/receive-invitation-url') - @ApiOperation({ summary: 'Receive Invitation URL', description: 'Receive an invitation URL for the organization.' }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async receiveInvitationUrl( - @Param('orgId') orgId: string, - @Body() receiveInvitationUrl: ReceiveInvitationUrlDto, - @User() user: IUserRequestInterface, - @Res() res: Response - ): Promise { - const connectionData = await this.connectionService.receiveInvitationUrl(receiveInvitationUrl, orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.receivenvitation, - data: connectionData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - /** - * Receive Invitation - * @param orgId The ID of the organization - * @param receiveInvitation The details of the invitation - * @param user The user making the request - * @param res The response object - * @returns The details of the received invitation - */ - @Post('/orgs/:orgId/receive-invitation') - @ApiOperation({ - summary: 'Receive Invitation', - description: 'Receive an invitation for the organization using the invitation object.' - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async receiveInvitation( - @Param('orgId') orgId: string, - @Body() receiveInvitation: ReceiveInvitationDto, - @User() user: IUserRequestInterface, - @Res() res: Response - ): Promise { - const connectionData = await this.connectionService.receiveInvitation(receiveInvitation, orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.receivenvitation, - data: connectionData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Create out-of-band connection invitation + * @param connectionDto + * @param res + * @returns Created out-of-band connection invitation url + */ + @Post('/orgs/:orgId/connections') + @ApiOperation({ summary: 'Create outbound out-of-band connection invitation', description: 'Create outbound out-of-band connection invitation' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async createConnectionInvitation( + @Param('orgId') orgId: string, + @Body() createOutOfBandConnectionInvitation: CreateOutOfBandConnectionInvitation, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + createOutOfBandConnectionInvitation.orgId = orgId; + const connectionData = await this.connectionService.createConnectionInvitation(createOutOfBandConnectionInvitation, reqUser); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.create, + data: connectionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + @Post('/orgs/:orgId/question-answer/question/:connectionId') + @ApiOperation({ summary: '', description: 'send question' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async sendQuestion( + @Param('orgId') orgId: string, + @Param('connectionId') connectionId: string, + @Body() questionDto: QuestionDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + questionDto.orgId = orgId; + questionDto.connectionId = connectionId; + const questionData = await this.connectionService.sendQuestion(questionDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.questionSend, + data: questionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + @Post('/orgs/:orgId/receive-invitation-url') + @ApiOperation({ summary: 'Receive Invitation URL', description: 'Receive Invitation URL' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async receiveInvitationUrl( + @Param('orgId') orgId: string, + @Body() receiveInvitationUrl: ReceiveInvitationUrlDto, + @User() user: IUserRequestInterface, + @Res() res: Response + ): Promise { + + const connectionData = await this.connectionService.receiveInvitationUrl(receiveInvitationUrl, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.receivenvitation, + data: connectionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Post('/orgs/:orgId/receive-invitation') + @ApiOperation({ summary: 'Receive Invitation', description: 'Receive Invitation' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async receiveInvitation( + @Param('orgId') orgId: string, + @Body() receiveInvitation: ReceiveInvitationDto, + @User() user: IUserRequestInterface, + @Res() res: Response + ): Promise { - /** - * Catch connection webhook responses - * @param connectionDto The details of the connection - * @param orgId The ID of the organization + const connectionData = await this.connectionService.receiveInvitation(receiveInvitation, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.receivenvitation, + data: connectionData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + /** + * Catch connection webhook responses. + * @Body connectionDto + * @param orgId * @returns Callback URL for connection and created connections details */ @Post('wh/:orgId/connections/') @ApiExcludeEndpoint() @ApiOperation({ summary: 'Catch connection webhook responses', - description: 'Receive connection webhook responses for the organization.' + description: 'Callback URL for connection' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) async getConnectionWebhook( @@ -369,83 +279,67 @@ export class ConnectionController { @Res() res: Response ): Promise { connectionDto.type = 'Connection'; - this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); + this.logger.log(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); + this.logger.debug(`connectionDto ::: ${JSON.stringify(connectionDto)} ${orgId}`); + if (orgId && 'default' === connectionDto?.contextCorrelationId) { connectionDto.orgId = orgId; } - const connectionData = await this.connectionService.getConnectionWebhook(connectionDto, orgId).catch((error) => { - this.logger.debug(`error in saving connection webhook ::: ${JSON.stringify(error)}`); - }); + const orgAgent = await this.connectionService.getConnectionWebhook(connectionDto, orgId).catch(error => { + this.logger.debug(`error in saving connection webhook ::: ${JSON.stringify(error)}`); + }); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, - data: connectionData + data: orgAgent }; - const webhookUrl = await this.connectionService - ._getWebhookUrl(connectionDto?.contextCorrelationId, orgId) - .catch((error) => { - this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`); - }); - if (webhookUrl) { - await this.connectionService._postWebhookResponse(webhookUrl, { data: connectionDto }).catch((error) => { - this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); - }); - } + const webhookUrl = orgAgent ? orgAgent.webhookUrl : false; + if (webhookUrl) { + await this.connectionService._postWebhookResponse(webhookUrl, { data: connectionDto }).catch(error => { + this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); + }); + } return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Catch question-answer webhook responses - * @param questionAnswerWebhookDto The details of the question-answer webhook - * @param orgId The ID of the organization - * @returns Callback URL for question-answer - */ + @Post('wh/:orgId/question-answer/') @ApiExcludeEndpoint() @ApiOperation({ summary: 'Catch question-answer webhook responses', - description: 'Receive question-answer webhook responses for the organization.' + description: 'Callback URL for question-answer' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) async getQuestionAnswerWebhook( - @Body() questionAnswerWebhookDto: QuestionAnswerWebhookDto, + @Body() questionAnswerWebhookDto:QuestionAnswerWebhookDto, @Param('orgId') orgId: string, @Res() res: Response ): Promise { questionAnswerWebhookDto.type = 'question-answer'; this.logger.debug(`questionAnswer ::: ${JSON.stringify(questionAnswerWebhookDto)} ${orgId}`); - + const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.connection.success.create, data: '' }; - const webhookUrl = await this.connectionService - ._getWebhookUrl(questionAnswerWebhookDto?.contextCorrelationId, orgId) - .catch((error) => { + const webhookUrl = await this.connectionService._getWebhookUrl(questionAnswerWebhookDto?.contextCorrelationId, orgId).catch(error => { this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`); - }); - + + }); + if (webhookUrl) { - await this.connectionService - ._postWebhookResponse(webhookUrl, { data: questionAnswerWebhookDto }) - .catch((error) => { - this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); + await this.connectionService._postWebhookResponse(webhookUrl, { data: questionAnswerWebhookDto }).catch(error => { + this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); }); - } + } return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Delete connection record - * @param orgId The ID of the organization - * @param user The user making the request - * @param res The response object - * @returns The status of the deletion operation - */ + @Delete('/orgs/:orgId/connections') - @ApiOperation({ summary: 'Delete connection record', description: 'Delete connection records by orgId' }) + @ApiOperation({ summary: 'Delete connection record', description: 'Delete connections by orgId' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER) @@ -470,56 +364,29 @@ export class ConnectionController { }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Send basic message - * @param orgId The ID of the organization - * @param connectionId The ID of the connection - * @param basicMessageDto The details of the basic message - * @param reqUser The user making the request - * @param res The response object - * @returns The details of the sent basic message - */ + @Post('/orgs/:orgId/basic-message/:connectionId') - @ApiOperation({ - summary: 'Send basic message', - description: 'Send a basic message to a specific connection for a specific organization.' - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles( - OrgRoles.OWNER, - OrgRoles.ADMIN, - OrgRoles.ISSUER, - OrgRoles.VERIFIER, - OrgRoles.MEMBER, - OrgRoles.HOLDER, - OrgRoles.SUPER_ADMIN, - OrgRoles.PLATFORM_ADMIN - ) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async sendBasicMessage( - @Param('orgId') orgId: string, - @Param( - 'connectionId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.connection.error.invalidConnectionId); - } - }) - ) - connectionId: string, - @Body() basicMessageDto: BasicMessageDto, - @User() reqUser: IUserRequestInterface, - @Res() res: Response - ): Promise { - basicMessageDto.orgId = orgId; - basicMessageDto.connectionId = connectionId; - const basicMesgResponse = await this.connectionService.sendBasicMessage(basicMessageDto); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.connection.success.basicMessage, - data: basicMesgResponse - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + @ApiOperation({ summary: 'Send basic message', description: 'Send basic message' }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER, OrgRoles.SUPER_ADMIN, OrgRoles.PLATFORM_ADMIN) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async sendBasicMessage( + @Param('orgId') orgId: string, + @Param('connectionId') connectionId: string, + @Body() basicMessageDto: BasicMessageDto, + @User() reqUser: IUserRequestInterface, + @Res() res: Response + ): Promise { + + basicMessageDto.orgId = orgId; + basicMessageDto.connectionId = connectionId; + const basicMesgResponse = await this.connectionService.sendBasicMessage(basicMessageDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.connection.success.basicMessage, + data: basicMesgResponse + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + + } } diff --git a/apps/api-gateway/src/connection/connection.module.ts b/apps/api-gateway/src/connection/connection.module.ts index 0a578b400..f8047bfd3 100644 --- a/apps/api-gateway/src/connection/connection.module.ts +++ b/apps/api-gateway/src/connection/connection.module.ts @@ -4,7 +4,6 @@ import { ConnectionService } from './connection.service'; import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -18,7 +17,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [ConnectionController], - providers: [ConnectionService, NATSClient] + providers: [ConnectionService] }) export class ConnectionModule { diff --git a/apps/api-gateway/src/connection/connection.service.ts b/apps/api-gateway/src/connection/connection.service.ts index 88efdbad7..cd3f0185d 100644 --- a/apps/api-gateway/src/connection/connection.service.ts +++ b/apps/api-gateway/src/connection/connection.service.ts @@ -1,56 +1,47 @@ import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable} from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; -import { - ConnectionDto, - CreateOutOfBandConnectionInvitation, - ReceiveInvitationDto, - ReceiveInvitationUrlDto -} from './dtos/connection.dto'; +import { ConnectionDto, CreateOutOfBandConnectionInvitation, ReceiveInvitationDto, ReceiveInvitationUrlDto } from './dtos/connection.dto'; import { IReceiveInvitationRes, IUserRequestInterface } from './interfaces'; -import { IConnectionList, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; -import { - AgentConnectionSearchCriteria, - IConnectionDetailsById, - IConnectionSearchCriteria -} from '../interfaces/IConnectionSearch.interface'; +import { IConnectionList, IDeletedConnectionsRecord, orgAgents } from '@credebl/common/interfaces/connection.interface'; +import { AgentConnectionSearchCriteria, IConnectionDetailsById, IConnectionSearchCriteria } from '../interfaces/IConnectionSearch.interface'; import { BasicMessageDto, QuestionDto } from './dtos/question-answer.dto'; +// eslint-disable-next-line camelcase import { user } from '@prisma/client'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { firstValueFrom } from 'rxjs'; @Injectable() export class ConnectionService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly connectionServiceProxy: ClientProxy) { super('ConnectionService'); } - sendQuestion(questionDto: QuestionDto): Promise { + sendQuestion( + questionDto: QuestionDto + ): Promise { try { - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'send-question', questionDto); + return this.sendNatsMessage(this.connectionServiceProxy, 'send-question', questionDto); } catch (error) { throw new RpcException(error.response); } } - sendBasicMessage(basicMessageDto: BasicMessageDto): Promise { + sendBasicMessage( + basicMessageDto: BasicMessageDto + ): Promise { try { - return this.natsClient.sendNatsMessage( - this.connectionServiceProxy, - 'send-basic-message-on-connection', - basicMessageDto - ); + return this.sendNatsMessage(this.connectionServiceProxy, 'send-basic-message-on-connection', basicMessageDto); } catch (error) { throw new RpcException(error.response); } } - getConnectionWebhook(connectionDto: ConnectionDto, orgId: string): Promise { + getConnectionWebhook( + connectionDto: ConnectionDto, + orgId: string + // eslint-disable-next-line camelcase + ): Promise { const payload = { connectionDto, orgId }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'webhook-get-connection', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'webhook-get-connection', payload); } getUrl(referenceId: string): Promise<{ @@ -58,7 +49,7 @@ export class ConnectionService extends BaseService { }> { try { const connectionDetails = { referenceId }; - return this.natsClient.sendNats(this.connectionServiceProxy, 'get-connection-url', connectionDetails); + return this.sendNats(this.connectionServiceProxy, 'get-connection-url', connectionDetails); } catch (error) { throw new RpcException(error.response); } @@ -70,7 +61,7 @@ export class ConnectionService extends BaseService { orgId: string ): Promise { const payload = { connectionSearchCriteria, user, orgId }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'get-all-connections', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-connections', payload); } getConnectionListFromAgent( @@ -78,20 +69,24 @@ export class ConnectionService extends BaseService { orgId: string ): Promise { const payload = { connectionSearchCriteria, orgId }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'get-all-agent-connection-list', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'get-all-agent-connection-list', payload); } - getConnectionsById(user: IUserRequest, connectionId: string, orgId: string): Promise { + getConnectionsById( + user: IUserRequest, + connectionId: string, + orgId: string + ): Promise { const payload = { user, connectionId, orgId }; - return this.natsClient.sendNatsMessage( - this.connectionServiceProxy, - 'get-connection-details-by-connectionId', - payload - ); + return this.sendNatsMessage(this.connectionServiceProxy, 'get-connection-details-by-connectionId', payload); } - getQuestionAnswersRecord(orgId: string): Promise { - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'get-question-answer-record', orgId); + + getQuestionAnswersRecord( + orgId: string + ): Promise { + + return this.sendNatsMessage(this.connectionServiceProxy, 'get-question-answer-record', orgId); } receiveInvitationUrl( @@ -100,7 +95,7 @@ export class ConnectionService extends BaseService { user: IUserRequestInterface ): Promise { const payload = { user, receiveInvitationUrl, orgId }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation-url', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation-url', payload); } receiveInvitation( @@ -109,17 +104,17 @@ export class ConnectionService extends BaseService { user: IUserRequestInterface ): Promise { const payload = { user, receiveInvitation, orgId }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'receive-invitation', payload); } async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { const pattern = { cmd: 'get-webhookurl' }; const payload = { tenantId, orgId }; - + try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message: string = await firstValueFrom(this.connectionServiceProxy.send(pattern, payload)); + const message = await this.connectionServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); @@ -127,12 +122,13 @@ export class ConnectionService extends BaseService { } } - async _postWebhookResponse(webhookUrl: string, data: object): Promise { + async _postWebhookResponse(webhookUrl: string, data:object): Promise { const pattern = { cmd: 'post-webhook-response-to-webhook-url' }; - const payload = { webhookUrl, data }; - + const payload = { webhookUrl, data }; + try { - const message: string = await firstValueFrom(this.connectionServiceProxy.send(pattern, payload)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.connectionServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); @@ -145,11 +141,11 @@ export class ConnectionService extends BaseService { user: IUserRequestInterface ): Promise { const payload = { user, createOutOfBandConnectionInvitation }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'create-connection-invitation', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'create-connection-invitation', payload); } async deleteConnectionRecords(orgId: string, userDetails: user): Promise { const payload = { orgId, userDetails }; - return this.natsClient.sendNatsMessage(this.connectionServiceProxy, 'delete-connection-records', payload); + return this.sendNatsMessage(this.connectionServiceProxy, 'delete-connection-records', payload); } } diff --git a/apps/api-gateway/src/connection/enums/connections.enum.ts b/apps/api-gateway/src/connection/enums/connections.enum.ts index a15929cf6..064433a16 100644 --- a/apps/api-gateway/src/connection/enums/connections.enum.ts +++ b/apps/api-gateway/src/connection/enums/connections.enum.ts @@ -1,4 +1,4 @@ export declare enum HandshakeProtocol { - Connections = "https://didcomm.org/connections/1.0", - DidExchange = "https://didcomm.org/didexchange/1.0" + Connections = 'https://didcomm.org/connections/1.0', + DidExchange = 'https://didcomm.org/didexchange/1.0' } \ No newline at end of file diff --git a/apps/api-gateway/src/connection/interfaces/index.ts b/apps/api-gateway/src/connection/interfaces/index.ts index 20f8f584b..dbbd4174c 100644 --- a/apps/api-gateway/src/connection/interfaces/index.ts +++ b/apps/api-gateway/src/connection/interfaces/index.ts @@ -71,8 +71,8 @@ interface IOutOfBandInvitationService { } interface IOutOfBandInvitation { - "@type": string; - "@id": string; + '@type': string; + '@id': string; label: string; accept: string[]; handshake_protocols: string[]; diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.spec.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.spec.ts index 2ad6e017a..fafb5851b 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.spec.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.spec.ts @@ -1,6 +1,7 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable camelcase */ import { Test, TestingModule } from '@nestjs/testing'; - -import { Any } from 'typeorm'; import { CredentialDefinitionController } from './credential-definition.controller'; import { CredentialDefinitionService } from './credential-definition.service'; diff --git a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts index 2fce93717..8ca949b27 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.controller.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.controller.ts @@ -1,27 +1,6 @@ -import { - Controller, - Logger, - Post, - Body, - UseGuards, - Get, - Query, - HttpStatus, - Res, - Param, - UseFilters, - ParseUUIDPipe, - BadRequestException -} from '@nestjs/common'; +import { Controller, Logger, Post, Body, UseGuards, Get, Query, HttpStatus, Res, Param, UseFilters, ParseUUIDPipe, BadRequestException } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; -import { - ApiTags, - ApiOperation, - ApiResponse, - ApiBearerAuth, - ApiUnauthorizedResponse, - ApiForbiddenResponse -} from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiUnauthorizedResponse, ApiForbiddenResponse } from '@nestjs/swagger'; import { ApiResponseDto } from 'apps/api-gateway/src/dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from 'apps/api-gateway/src/dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from 'apps/api-gateway/src/dtos/forbidden-error.dto'; @@ -37,43 +16,34 @@ import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { Roles } from '../authz/decorators/roles.decorator'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -import { EmptyStringParamPipe, TrimStringParamPipe } from '@credebl/common/cast.helper'; +import { TrimStringParamPipe } from '@credebl/common/cast.helper'; + @ApiBearerAuth() @ApiTags('credential-definitions') @Controller() -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @UseFilters(CustomExceptionFilter) export class CredentialDefinitionController { - constructor(private readonly credentialDefinitionService: CredentialDefinitionService) {} + + constructor(private readonly credentialDefinitionService: CredentialDefinitionService) { } private readonly logger = new Logger('CredentialDefinitionController'); - /** - * Retrieves the details of a specific credential definition. - * - * @param orgId The unique identifier of the organization. - * @param credDefId The unique identifier of the credential definition. - * @returns The credential definition details. - */ @Get('/orgs/:orgId/cred-defs/:credDefId') @ApiOperation({ summary: 'Get credential definition by credential definition Id', - description: - 'Fetches the details of a specific credential definition using its ID available credential definitions on platform.' + description: 'Get credential definition details by credential definition Id' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getCredentialDefinitionById( - @Param('orgId') orgId: string, - @Param('credDefId', TrimStringParamPipe, EmptyStringParamPipe.forParam('credDefId')) credentialDefinitionId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Param('credDefId') credentialDefinitionId: string, @Res() res: Response ): Promise { - const credentialsDefinitionDetails = await this.credentialDefinitionService.getCredentialDefinitionById( - credentialDefinitionId, - orgId - ); + const credentialsDefinitionDetails = await this.credentialDefinitionService.getCredentialDefinitionById(credentialDefinitionId, orgId); const credDefResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.credentialDefinition.success.fetch, @@ -82,17 +52,10 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - /** - * Retrieves all credential definitions linked to a specific schema. - * - * @param schemaId The unique identifier of the schema. - * @returns A list of credential definitions associated with the schema. - */ @Get('/verifiation/cred-defs/:schemaId') @ApiOperation({ summary: 'Get all credential definitions by schema Id', - description: - 'Fetches all credential definitions associated with a specific schema ID available credential definitions on platform.' + description: 'Get all credential definitions by schema Id for verification' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt')) @@ -100,10 +63,7 @@ export class CredentialDefinitionController { @Param('schemaId', TrimStringParamPipe) schemaId: string, @Res() res: Response ): Promise { - if (!schemaId) { - throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); - } - + const credentialsDefinitions = await this.credentialDefinitionService.getCredentialDefinitionBySchemaId(schemaId); const credDefResponse: IResponse = { statusCode: HttpStatus.OK, @@ -113,29 +73,15 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - /** - * Retrieves all credential definitions for a given organization. - * - * @param orgId The unique identifier of the organization. - * @returns A paginated list of credential definitions for the organization. - */ @Get('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Fetch all credential definitions by organization Id', - description: 'Fetches all credential definitions belonging to a specific organization created on the platform.' + description: 'Fetch all credential definitions of provided organization Id with pagination' }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getAllCredDefs( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Query() getAllCredDefs: GetAllCredDefsDto, @User() user: IUserRequestInterface, @Res() res: Response @@ -153,17 +99,10 @@ export class CredentialDefinitionController { return res.status(HttpStatus.OK).json(credDefResponse); } - /** - * Creates a new credential definition and submits it to the ledger. - * - * @param orgId The unique identifier of the organization. - * @body CreateCredentialDefinitionDto - * @returns The details of the newly created credential definition. - */ @Post('/orgs/:orgId/cred-defs') @ApiOperation({ summary: 'Sends a credential definition to ledger', - description: 'Creates a new credential definition and submits it to the ledger.' + description: 'Sends a credential definition to ledger' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -171,22 +110,12 @@ export class CredentialDefinitionController { async createCredentialDefinition( @User() user: IUserRequestInterface, @Body() credDef: CreateCredentialDefinitionDto, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response ): Promise { + credDef.orgId = orgId; - const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition( - credDef, - user - ); + const credentialsDefinitionDetails = await this.credentialDefinitionService.createCredentialDefinition(credDef, user); const credDefResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.credentialDefinition.success.create, @@ -194,4 +123,5 @@ export class CredentialDefinitionController { }; return res.status(HttpStatus.CREATED).json(credDefResponse); } -} + +} \ No newline at end of file diff --git a/apps/api-gateway/src/credential-definition/credential-definition.module.ts b/apps/api-gateway/src/credential-definition/credential-definition.module.ts index cd99e5b3f..769b72109 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.module.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.module.ts @@ -5,7 +5,6 @@ import { CredentialDefinitionController } from './credential-definition.controll import { CredentialDefinitionService } from './credential-definition.service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports:[ @@ -18,7 +17,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [CredentialDefinitionController], - providers: [CredentialDefinitionService, NATSClient] + providers: [CredentialDefinitionService] }) export class CredentialDefinitionModule { constructor() { diff --git a/apps/api-gateway/src/credential-definition/credential-definition.service.ts b/apps/api-gateway/src/credential-definition/credential-definition.service.ts index 4f23b357c..ccf9981d2 100644 --- a/apps/api-gateway/src/credential-definition/credential-definition.service.ts +++ b/apps/api-gateway/src/credential-definition/credential-definition.service.ts @@ -1,47 +1,38 @@ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { CreateCredentialDefinitionDto } from './dto/create-cred-defs.dto'; import { BaseService } from '../../../../libs/service/base.service'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; import { GetAllCredDefsDto } from '../dtos/get-cred-defs.dto'; import { ICredDef, ICredDefs } from './interfaces'; import { ICredDefData } from '@credebl/common/interfaces/cred-def.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class CredentialDefinitionService extends BaseService { + constructor( - @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy, - private readonly natsClient: NATSClient + @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy ) { super('CredentialDefinitionService'); } createCredentialDefinition(credDef: CreateCredentialDefinitionDto, user: IUserRequestInterface): Promise { - const payload = { credDef, user }; - return this.natsClient.sendNatsMessage(this.credDefServiceProxy, 'create-credential-definition', payload); + const payload = { credDef, user }; + return this.sendNatsMessage(this.credDefServiceProxy, 'create-credential-definition', payload); } getCredentialDefinitionById(credentialDefinitionId: string, orgId: string): Promise { const payload = { credentialDefinitionId, orgId }; - return this.natsClient.sendNatsMessage(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); + return this.sendNatsMessage(this.credDefServiceProxy, 'get-credential-definition-by-id', payload); } - getAllCredDefs( - credDefSearchCriteria: GetAllCredDefsDto, - user: IUserRequestInterface, - orgId: string - ): Promise { + getAllCredDefs(credDefSearchCriteria: GetAllCredDefsDto, user: IUserRequestInterface, orgId: string): Promise { const payload = { credDefSearchCriteria, user, orgId }; - return this.natsClient.sendNatsMessage(this.credDefServiceProxy, 'get-all-credential-definitions', payload); + return this.sendNatsMessage(this.credDefServiceProxy, 'get-all-credential-definitions', payload); } getCredentialDefinitionBySchemaId(schemaId: string): Promise { const payload = { schemaId }; - return this.natsClient.sendNatsMessage( - this.credDefServiceProxy, - 'get-all-credential-definitions-by-schema-id', - payload - ); + return this.sendNatsMessage(this.credDefServiceProxy, 'get-all-credential-definitions-by-schema-id', payload); } } diff --git a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts index 2187ad362..8c825d65b 100644 --- a/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/create-cred-defs.dto.ts @@ -1,20 +1,18 @@ import { IsBoolean, IsDefined, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; export class CreateCredentialDefinitionDto { @ApiProperty({ 'example': 'default' }) - @IsDefined({ message: 'Tag is required' }) + @IsDefined({ message: 'Tag is required.' }) @IsNotEmpty({ message: 'Please provide a tag' }) - @IsString({ message: 'Tag should be string' }) + @IsString({ message: 'Tag id should be string' }) tag: string; @ApiProperty({ 'example': 'WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0' }) - @IsDefined({ message: 'schemaLedgerId is required' }) - @IsNotEmpty({ message: 'Please provide valid schema ledger Id' }) - @Transform(({ value }) => trim(value)) + @IsDefined({ message: 'schemaLedgerId is required.' }) + @IsNotEmpty({ message: 'Please provide a schema id' }) @IsString({ message: 'Schema id should be string' }) schemaLedgerId: string; diff --git a/apps/api-gateway/src/credential-definition/dto/get-all-platform-cred-defs.dto.ts b/apps/api-gateway/src/credential-definition/dto/get-all-platform-cred-defs.dto.ts index 20f4fb34c..1224f50f0 100644 --- a/apps/api-gateway/src/credential-definition/dto/get-all-platform-cred-defs.dto.ts +++ b/apps/api-gateway/src/credential-definition/dto/get-all-platform-cred-defs.dto.ts @@ -8,11 +8,11 @@ import { IsEnum, IsOptional, IsUUID } from 'class-validator'; export class GetAllPlatformCredDefsDto extends PaginationDto { @ApiProperty({ example: '1a7eac11-ff05-40d7-8351-4d7467687cad'}) + @Transform(({ value }) => trim(value)) @ApiPropertyOptional() - @Transform(({ value }) => ('string' === typeof value && '' === value.trim() ? undefined : value.trim())) @IsOptional() @IsUUID('4', { message: 'Invalid format of ledgerId' }) - ledgerId?: string; + ledgerId: string; @ApiProperty({ required: false diff --git a/apps/api-gateway/src/dtos/apiResponse.dto copy.ts b/apps/api-gateway/src/dtos/apiResponse.dto copy.ts index 60fa37163..f33e8becf 100644 --- a/apps/api-gateway/src/dtos/apiResponse.dto copy.ts +++ b/apps/api-gateway/src/dtos/apiResponse.dto copy.ts @@ -8,6 +8,7 @@ export class ApiResponseDto { success: boolean; @ApiProperty() + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; @ApiProperty({ example: 200 }) diff --git a/apps/api-gateway/src/dtos/authDto.dto.ts b/apps/api-gateway/src/dtos/authDto.dto.ts index 39a6d00a0..fc53b03a8 100644 --- a/apps/api-gateway/src/dtos/authDto.dto.ts +++ b/apps/api-gateway/src/dtos/authDto.dto.ts @@ -5,7 +5,7 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class AuthDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/connection-out-of-band.dto.ts b/apps/api-gateway/src/dtos/connection-out-of-band.dto.ts index bdfc6f1fc..669206395 100644 --- a/apps/api-gateway/src/dtos/connection-out-of-band.dto.ts +++ b/apps/api-gateway/src/dtos/connection-out-of-band.dto.ts @@ -1,5 +1,6 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsString, IsNotEmpty, IsObject, IsNegative } from 'class-validator'; +import { IsArray, IsNotEmpty } from 'class-validator'; interface attachmentsObject{ diff --git a/apps/api-gateway/src/dtos/connection.dto.ts b/apps/api-gateway/src/dtos/connection.dto.ts index 3df7acb17..486a745bc 100644 --- a/apps/api-gateway/src/dtos/connection.dto.ts +++ b/apps/api-gateway/src/dtos/connection.dto.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; export class ConnectionDto { diff --git a/apps/api-gateway/src/dtos/create-revocation-registry.dto.ts b/apps/api-gateway/src/dtos/create-revocation-registry.dto.ts index efc0cc2b9..d2bf6cbca 100644 --- a/apps/api-gateway/src/dtos/create-revocation-registry.dto.ts +++ b/apps/api-gateway/src/dtos/create-revocation-registry.dto.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; diff --git a/apps/api-gateway/src/dtos/create-schema.dto.ts b/apps/api-gateway/src/dtos/create-schema.dto.ts index 91674834d..bac862dfb 100644 --- a/apps/api-gateway/src/dtos/create-schema.dto.ts +++ b/apps/api-gateway/src/dtos/create-schema.dto.ts @@ -1,347 +1,171 @@ -import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; -import { - ArrayMinSize, - IsArray, - IsBoolean, - IsEnum, - IsInt, - IsNotEmpty, - IsNumber, - IsOptional, - IsPositive, - IsString, - Min, - ValidateIf, - ValidateNested -} from 'class-validator'; -import { IndySchemaDataType, JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; -import { IsNotSQLInjection, ValidateNestedStructureFields, trim } from '@credebl/common/cast.helper'; -import { Transform, Type, plainToClass } from 'class-transformer'; - -export class W3CAttributeValue { - @ApiProperty() - @IsString() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'attributeName is required' }) - attributeName: string; - - @ApiProperty() - @IsString() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'displayName is required' }) - displayName: string; - - @ApiProperty({ - description: 'The type of the schema', - enum: W3CSchemaDataType, - example: W3CSchemaDataType.STRING - }) - @IsEnum(W3CSchemaDataType, { - message: `Schema data type must be one of [${Object.values(W3CSchemaDataType).join(', ')}]` - }) - @ValidateNestedStructureFields() - schemaDataType: W3CSchemaDataType; - - @ApiProperty() - @IsBoolean() - @IsNotEmpty({ message: 'isRequired property is required' }) - isRequired: boolean; - - @ApiPropertyOptional() - @IsOptional() - description?: string; - - @ApiPropertyOptional({ description: 'Minimum length for string values' }) - @IsOptional() - @IsInt() - @Min(0) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - minLength?: number; - - @ApiPropertyOptional({ description: 'Maximum length for string values' }) - @IsOptional() - @IsInt() - @Min(1) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - maxLength?: number; - - @ApiPropertyOptional({ description: 'Regular expression pattern for string values' }) - @IsOptional() - @IsString() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - pattern?: string; - - @ApiPropertyOptional({ description: 'Enumerated values for string type' }) - @IsOptional() - @IsArray() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - enum?: string[]; - - @ApiPropertyOptional({ description: 'Content encoding (e.g., base64)' }) - @IsOptional() - @IsString() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - contentEncoding?: string; - - @ApiPropertyOptional({ description: 'Content media type (e.g., image/png)' }) - @IsOptional() - @IsString() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.STRING) - contentMediaType?: string; - - // Number type specific validations - @ApiPropertyOptional({ description: 'Minimum value (inclusive) for number values' }) - @IsOptional() - @IsNumber() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.NUMBER) - minimum?: number; - - @ApiPropertyOptional({ description: 'Maximum value (inclusive) for number values' }) - @IsOptional() - @IsNumber() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.NUMBER) - maximum?: number; - - @ApiPropertyOptional({ description: 'Minimum value (exclusive) for number values' }) - @IsOptional() - @IsNumber() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.NUMBER) - exclusiveMinimum?: number; - - @ApiPropertyOptional({ description: 'Maximum value (exclusive) for number values' }) - @IsOptional() - @IsNumber() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.NUMBER) - exclusiveMaximum?: number; - - @ApiPropertyOptional({ description: 'Number must be a multiple of this value' }) - @IsOptional() - @IsNumber() - @IsPositive() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.NUMBER) - multipleOf?: number; - - // Array type specific validations - @ApiPropertyOptional({ description: 'Minimum number of items in array' }) - @IsOptional() - @IsInt() - @Min(0) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.ARRAY) - minItems?: number; - - @ApiPropertyOptional({ description: 'Maximum number of items in array' }) - @IsOptional() - @IsInt() - @Min(1) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.ARRAY) - maxItems?: number; - - @ApiPropertyOptional({ description: 'Whether array items must be unique' }) - @IsOptional() - @IsBoolean() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.ARRAY) - uniqueItems?: boolean; - - @ApiPropertyOptional({ description: 'Array of items', type: [W3CAttributeValue] }) - @IsArray() - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => W3CAttributeValue) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.ARRAY) - items?: W3CAttributeValue[]; - - // Object type specific validations - @ApiPropertyOptional({ description: 'Minimum number of properties in object' }) - @IsOptional() - @IsInt() - @Min(0) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - minProperties?: number; - - @ApiPropertyOptional({ description: 'Maximum number of properties in object' }) - @IsOptional() - @IsInt() - @Min(1) - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - maxProperties?: number; - - @ApiPropertyOptional({ description: 'additional properties must be boolean' }) - @IsOptional() - @IsBoolean() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - additionalProperties?: boolean; - - @ApiPropertyOptional({ description: 'Required properties for object type' }) - @IsOptional() - @IsArray() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - required?: string[]; - - @ApiPropertyOptional({ description: 'Dependent required properties' }) - @IsOptional() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - dependentRequired?: Record; - - @ApiPropertyOptional({ description: 'Object with dynamic properties' }) - @IsOptional() - @ValidateIf((o) => o.schemaDataType === W3CSchemaDataType.OBJECT) - @Transform(({ value }) => { - if (value && 'object' === typeof value) { - const result = {}; - Object.entries(value).forEach(([key, propValue]) => { - result[key] = plainToClass(W3CAttributeValue, propValue, { - enableImplicitConversion: false - }); - }); - return result; - } - return value; - }) - properties?: Record; -} +import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; +import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; + + class W3CAttributeValue { + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'attributeName is required' }) + attributeName: string; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'displayName is required' }) + displayName: string; + + @ApiProperty({ + description: 'The type of the schema', + enum: W3CSchemaDataType, + example: W3CSchemaDataType.STRING + }) + @IsEnum(W3CSchemaDataType, { message: 'Schema data type must be a valid type' }) + schemaDataType: W3CSchemaDataType; + + @ApiProperty() + @IsBoolean() + @IsNotEmpty({ message: 'isRequired property is required' }) + isRequired: boolean; + } class AttributeValue { - @ApiProperty() - @IsString() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'attributeName is required' }) - attributeName: string; - - @ApiProperty({ - description: 'The type of the schema', - enum: IndySchemaDataType, - example: IndySchemaDataType.STRING - }) - @IsString() - @Transform(({ value }) => trim(value)) - @IsEnum(IndySchemaDataType, { - message: `Schema data type must be one of [${Object.values(IndySchemaDataType).join(', ')}]` - }) - schemaDataType: IndySchemaDataType; - - @ApiProperty() - @IsString() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'displayName is required' }) - displayName: string; - @ApiProperty() - @IsBoolean() - @IsNotEmpty({ message: 'isRequired property is required' }) - isRequired: boolean; + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'attributeName is required' }) + attributeName: string; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaDataType is required' }) + schemaDataType: string; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'displayName is required' }) + displayName: string; + + @ApiProperty() + @IsBoolean() + @IsNotEmpty({ message: 'isRequired property is required' }) + isRequired: boolean; } export class CreateSchemaDto { - @ApiProperty() - @IsString({ message: 'schemaVersion must be a string' }) - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'schemaVersion is required' }) - schemaVersion: string; - - @ApiProperty() - @IsString({ message: 'schemaName must be a string' }) - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'schemaName is required' }) - @IsNotSQLInjection({ message: 'SchemaName is required.' }) - schemaName: string; - - @ApiProperty({ - type: [AttributeValue], - example: [ - { - attributeName: 'name', - schemaDataType: 'string', - displayName: 'Name', - isRequired: true - } - ] - }) - @IsArray({ message: 'attributes must be an array' }) - @IsNotEmpty({ message: 'attributes are required' }) - @ArrayMinSize(1) - @ValidateNested({ each: true }) - @Type(() => AttributeValue) - attributes: AttributeValue[]; - - orgId: string; - - @ApiPropertyOptional() - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsNotEmpty({ message: 'orgDid should not be empty' }) - @IsString({ message: 'orgDid must be a string' }) - orgDid: string; + @ApiProperty() + @IsString({ message: 'schemaVersion must be a string' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaVersion is required' }) + schemaVersion: string; + + @ApiProperty() + @IsString({ message: 'schemaName must be a string' }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaName is required' }) + @IsNotSQLInjection({ message: 'SchemaName is required.' }) + schemaName: string; + + @ApiProperty({ + type: [AttributeValue], + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name', + isRequired: true + } + ] + }) + @IsArray({ message: 'attributes must be an array' }) + @IsNotEmpty({ message: 'attributes are required' }) + @ArrayMinSize(1) + @ValidateNested({ each: true }) + @Type(() => AttributeValue) + attributes: AttributeValue[]; + + orgId: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsNotEmpty({ message: 'orgDid should not be empty' }) + @IsString({ message: 'orgDid must be a string' }) + orgDid: string; } export class CreateW3CSchemaDto { - @ApiProperty({ - type: [W3CAttributeValue], - example: [ - { - attributeName: 'name', - schemaDataType: 'string', - displayName: 'Name', - isRequired: true - } - ] - }) - @ValidateNested({ each: true }) - @Type(() => W3CAttributeValue) - @IsArray({ message: 'attributes must be an array' }) - @ArrayMinSize(1) - @IsNotEmpty() - attributes: W3CAttributeValue[]; - - @ApiProperty() - @IsString({ message: 'schemaName must be a string' }) - @Transform(({ value }) => value.trim()) - @IsNotEmpty({ message: 'schemaName is required' }) - schemaName: string; - - @ApiProperty() - @IsString({ message: 'description must be a string' }) - @IsNotEmpty({ message: 'description is required' }) - description: string; - - @ApiProperty({ - description: 'The type of the schema', - enum: JSONSchemaType, - example: JSONSchemaType.POLYGON_W3C - }) - @IsEnum(JSONSchemaType, { message: 'Schema type must be a valid schema type' }) - @IsNotEmpty({ message: 'Type is required' }) - schemaType: JSONSchemaType; + @ApiProperty({ + type: [W3CAttributeValue], + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name', + isRequired: true + } + ] + }) + @ValidateNested({each: true}) + @Type(() => W3CAttributeValue) + @IsArray({ message: 'attributes must be an array' }) + @ArrayMinSize(1) + @IsNotEmpty() + attributes: W3CAttributeValue []; + + @ApiProperty() + @IsString({ message: 'schemaName must be a string' }) + @Transform(({ value }) => value.trim()) + @IsNotEmpty({ message: 'schemaName is required' }) + schemaName: string; + + @ApiProperty() + @IsString({ message: 'description must be a string' }) + @IsNotEmpty({ message: 'description is required' }) + description: string; + + @ApiProperty({ + description: 'The type of the schema', + enum: JSONSchemaType, + example: JSONSchemaType.POLYGON_W3C + }) + @IsEnum(JSONSchemaType, { message: 'Schema type must be a valid schema type' }) + @IsNotEmpty({ message: 'Type is required' }) + schemaType: JSONSchemaType; } @ApiExtraModels(CreateSchemaDto, CreateW3CSchemaDto) export class GenericSchemaDTO { - @ApiProperty({ - description: 'The type of the schema', - enum: SchemaTypeEnum, - example: SchemaTypeEnum.INDY - }) - @IsEnum(SchemaTypeEnum, { message: 'Type must be a valid schema type' }) - @IsNotEmpty({ message: 'Type is required' }) - type: SchemaTypeEnum; - - @ApiPropertyOptional() - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsString({ message: 'alias must be a string' }) - @IsNotEmpty({ message: 'alias is required' }) - alias: string; - - @ApiProperty({ - type: Object, - oneOf: [{ $ref: getSchemaPath(CreateSchemaDto) }, { $ref: getSchemaPath(CreateW3CSchemaDto) }] - }) - @ValidateNested() - @Type(({ object }) => { - if (object.type === SchemaTypeEnum.INDY) { - return CreateSchemaDto; - } else if (object.type === SchemaTypeEnum.JSON) { - return CreateW3CSchemaDto; - } - }) - schemaPayload: CreateSchemaDto | CreateW3CSchemaDto; -} + @ApiProperty({ + description: 'The type of the schema', + enum: SchemaTypeEnum, + example: SchemaTypeEnum.INDY + }) + @IsEnum(SchemaTypeEnum, { message: 'Type must be a valid schema type' }) + @IsNotEmpty({ message: 'Type is required' }) + type: SchemaTypeEnum; + + @ApiProperty({ + type: Object, + oneOf: [ + { $ref: getSchemaPath(CreateSchemaDto) }, + { $ref: getSchemaPath(CreateW3CSchemaDto) } + ] + }) + @ValidateNested() + @Type(({ object }) => { + if (object.type === SchemaTypeEnum.INDY) { + return CreateSchemaDto; + } else if (object.type === SchemaTypeEnum.JSON) { + return CreateW3CSchemaDto; + } + }) + schemaPayload:CreateSchemaDto | CreateW3CSchemaDto; + + +} \ No newline at end of file diff --git a/apps/api-gateway/src/dtos/created-response-dto.ts b/apps/api-gateway/src/dtos/created-response-dto.ts index 3c3beeb36..1bbf306b6 100644 --- a/apps/api-gateway/src/dtos/created-response-dto.ts +++ b/apps/api-gateway/src/dtos/created-response-dto.ts @@ -9,7 +9,7 @@ export class CreatedResponseDto { success: boolean; @ApiProperty() - data?: any; + data?: unknown; @ApiProperty({ example: HttpStatus.CREATED }) code?: number; diff --git a/apps/api-gateway/src/dtos/credential-offer.dto.ts b/apps/api-gateway/src/dtos/credential-offer.dto.ts index c59e54406..9100af9c2 100644 --- a/apps/api-gateway/src/dtos/credential-offer.dto.ts +++ b/apps/api-gateway/src/dtos/credential-offer.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsArray, IsNotEmpty, IsString } from "class-validator"; +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsString } from 'class-validator'; interface attributeValue { name: string, diff --git a/apps/api-gateway/src/dtos/credential-send-offer.dto.ts b/apps/api-gateway/src/dtos/credential-send-offer.dto.ts index 1467ef6af..bbae5d52d 100644 --- a/apps/api-gateway/src/dtos/credential-send-offer.dto.ts +++ b/apps/api-gateway/src/dtos/credential-send-offer.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsNotEmpty, IsString } from "class-validator"; +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; export class CredentialSendOffer { diff --git a/apps/api-gateway/src/dtos/email-validator.dto.ts b/apps/api-gateway/src/dtos/email-validator.dto.ts index 1ba458fcf..4580ffe48 100644 --- a/apps/api-gateway/src/dtos/email-validator.dto.ts +++ b/apps/api-gateway/src/dtos/email-validator.dto.ts @@ -6,7 +6,7 @@ import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator'; export class EmailValidator { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/forgot-password.dto.ts b/apps/api-gateway/src/dtos/forgot-password.dto.ts index f2f68f18a..1269be8ea 100644 --- a/apps/api-gateway/src/dtos/forgot-password.dto.ts +++ b/apps/api-gateway/src/dtos/forgot-password.dto.ts @@ -6,7 +6,7 @@ import { trim } from '@credebl/common/cast.helper'; export class ForgotPasswordDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/geo-location-dto.ts b/apps/api-gateway/src/dtos/geo-location-dto.ts deleted file mode 100644 index 06a3cf3ca..000000000 --- a/apps/api-gateway/src/dtos/geo-location-dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; - -@ApiExtraModels() -export class GeoLocationDto { - @ApiPropertyOptional({ example: 101 }) - @IsOptional() - @IsNotEmpty({ message: 'country is required' }) - @IsNumber({}, { message: 'countryId must be a number' }) - countryId?: number; - - @ApiPropertyOptional({ example: 4008 }) - @IsOptional() - @IsNotEmpty({ message: 'state is required' }) - @IsNumber({}, { message: 'stateId must be a number' }) - stateId?: number; - - @ApiPropertyOptional({ example: 1000 }) - @IsOptional() - @IsNotEmpty({ message: 'city is required' }) - @IsNumber({}, { message: 'cityId must be a number' }) - cityId?: number; -} diff --git a/apps/api-gateway/src/dtos/holder-details.dto.ts b/apps/api-gateway/src/dtos/holder-details.dto.ts index b3fb3c009..1848fb95a 100644 --- a/apps/api-gateway/src/dtos/holder-details.dto.ts +++ b/apps/api-gateway/src/dtos/holder-details.dto.ts @@ -15,7 +15,7 @@ export class HolderDetailsDto { @IsString({ message: 'lastName should be string' }) lastName: string; - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/holder-reset-password.ts b/apps/api-gateway/src/dtos/holder-reset-password.ts index dbd25709e..e74f0055b 100644 --- a/apps/api-gateway/src/dtos/holder-reset-password.ts +++ b/apps/api-gateway/src/dtos/holder-reset-password.ts @@ -5,7 +5,7 @@ import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class HolderResetPasswordDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/issue-credential-offer.dto .ts b/apps/api-gateway/src/dtos/issue-credential-offer.dto .ts index 9eed1f5ed..285646add 100644 --- a/apps/api-gateway/src/dtos/issue-credential-offer.dto .ts +++ b/apps/api-gateway/src/dtos/issue-credential-offer.dto .ts @@ -1,5 +1,6 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsNotEmpty, IsBoolean, IsNotEmptyObject, IsObject } from 'class-validator'; +import { IsString, IsNotEmpty, IsBoolean, IsObject } from 'class-validator'; interface ICredAttrSpec { 'mime-type': string, name: string, diff --git a/apps/api-gateway/src/dtos/issue-credential-out-of-band.dto.ts b/apps/api-gateway/src/dtos/issue-credential-out-of-band.dto.ts index d412d28a8..5085cb3f6 100644 --- a/apps/api-gateway/src/dtos/issue-credential-out-of-band.dto.ts +++ b/apps/api-gateway/src/dtos/issue-credential-out-of-band.dto.ts @@ -1,5 +1,6 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsString, IsNotEmpty, IsObject } from 'class-validator'; +import { IsString, IsNotEmpty, IsObject } from 'class-validator'; interface IssueCredAttrSpec { 'mime-type': string, diff --git a/apps/api-gateway/src/dtos/not-found-error.dto.ts b/apps/api-gateway/src/dtos/not-found-error.dto.ts index 3ea199978..41a307661 100644 --- a/apps/api-gateway/src/dtos/not-found-error.dto.ts +++ b/apps/api-gateway/src/dtos/not-found-error.dto.ts @@ -15,7 +15,7 @@ export class NotFoundErrorDto { success: boolean; @ApiProperty() - data?: boolean | {} | []; + data?: boolean | object | []; @ApiProperty({ example: 404 }) code: number; diff --git a/apps/api-gateway/src/dtos/register-non-admin-user.dto.ts b/apps/api-gateway/src/dtos/register-non-admin-user.dto.ts index ec9f3876e..d7f8ce3fa 100644 --- a/apps/api-gateway/src/dtos/register-non-admin-user.dto.ts +++ b/apps/api-gateway/src/dtos/register-non-admin-user.dto.ts @@ -14,7 +14,7 @@ export class RegisterNonAdminUserDto { @IsString({message:'LastName should be string'}) lastName: string; - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/register-tenant.dto.ts b/apps/api-gateway/src/dtos/register-tenant.dto.ts index 22bbbb89a..ee3dc8d77 100644 --- a/apps/api-gateway/src/dtos/register-tenant.dto.ts +++ b/apps/api-gateway/src/dtos/register-tenant.dto.ts @@ -9,7 +9,7 @@ import { toLowerCase, trim } from '@credebl/common/cast.helper'; @ApiExtraModels() export class RegisterTenantDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @Transform(({ value }) => toLowerCase(value)) @IsNotEmpty({ message: 'Email is required.' }) @MaxLength(256, { message: 'Email must be at most 256 character.' }) diff --git a/apps/api-gateway/src/dtos/register-user.dto.ts b/apps/api-gateway/src/dtos/register-user.dto.ts index 3bfb15ad2..1829eeb6d 100644 --- a/apps/api-gateway/src/dtos/register-user.dto.ts +++ b/apps/api-gateway/src/dtos/register-user.dto.ts @@ -8,7 +8,7 @@ export class RegisterUserDto { attribute: any; - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/reset-password.dto.ts b/apps/api-gateway/src/dtos/reset-password.dto.ts index cc855fa93..4fe6acc0d 100644 --- a/apps/api-gateway/src/dtos/reset-password.dto.ts +++ b/apps/api-gateway/src/dtos/reset-password.dto.ts @@ -3,7 +3,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsString, IsNotEmpty, IsEmail } from 'class-validator'; export class ResetPasswordDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) diff --git a/apps/api-gateway/src/dtos/revoke-credential.dto.ts b/apps/api-gateway/src/dtos/revoke-credential.dto.ts index 305ec6d10..224f038c8 100644 --- a/apps/api-gateway/src/dtos/revoke-credential.dto.ts +++ b/apps/api-gateway/src/dtos/revoke-credential.dto.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty, IsBoolean, IsNumber } from 'class-validator'; export class RevokeCredentialDto { diff --git a/apps/api-gateway/src/dtos/save-roles-permissions.dto.ts b/apps/api-gateway/src/dtos/save-roles-permissions.dto.ts index b884ac46d..ce01ac99a 100644 --- a/apps/api-gateway/src/dtos/save-roles-permissions.dto.ts +++ b/apps/api-gateway/src/dtos/save-roles-permissions.dto.ts @@ -1,14 +1,14 @@ import { ApiProperty } from '@nestjs/swagger'; export class RolesPermissionsObj { - @ApiProperty() - roleId: string; + @ApiProperty() + roleId: string; - @ApiProperty({ type: Number }) - permissionsId: number; + @ApiProperty({ type: [] }) + permissionsId: number; } export class SaveRolesPermissionsDto { - @ApiProperty({ type: RolesPermissionsObj }) - data: RolesPermissionsObj; + @ApiProperty({ type: [] }) + data: RolesPermissionsObj; } diff --git a/apps/api-gateway/src/dtos/schemasearch.dto.ts b/apps/api-gateway/src/dtos/schemasearch.dto.ts index 36e578d4a..6aca41b9a 100644 --- a/apps/api-gateway/src/dtos/schemasearch.dto.ts +++ b/apps/api-gateway/src/dtos/schemasearch.dto.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import { ApiProperty } from '@nestjs/swagger'; export class SchemaSearchDto { diff --git a/apps/api-gateway/src/dtos/update-revocation-registry.dto.ts b/apps/api-gateway/src/dtos/update-revocation-registry.dto.ts index 1449172a0..85086111b 100644 --- a/apps/api-gateway/src/dtos/update-revocation-registry.dto.ts +++ b/apps/api-gateway/src/dtos/update-revocation-registry.dto.ts @@ -3,6 +3,7 @@ import { ApiProperty } from '@nestjs/swagger'; export class UpdateRevocationRegistryUriDto { @ApiProperty({ 'example': 'WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:20:tag:CL_ACCUM:0' }) // tslint:disable-next-line: variable-name + // eslint-disable-next-line camelcase revoc_reg_id?: string; @ApiProperty({ 'example': 'http://192.168.56.133:5000/revocation/registry/WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:20:tag:CL_ACCUM:0/tails-file' }) // tslint:disable-next-line: variable-name diff --git a/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts new file mode 100644 index 000000000..429ddfba2 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/accept-reject-invitations.dto.ts @@ -0,0 +1,25 @@ +import { IsEnum, IsNotEmpty} from 'class-validator'; + +import { ApiProperty } from '@nestjs/swagger'; +import { Invitation } from '@credebl/enum/enum'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +export class AcceptRejectEcosystemInvitationDto { + + ecosystemId: string; + invitationId: string; + orgId: string; + + @ApiProperty({ + enum: [Invitation.ACCEPTED, Invitation.REJECTED] + }) + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Please provide valid status' }) + @IsEnum(Invitation) + status: Invitation.ACCEPTED | Invitation.REJECTED; + + userId?: string; + + +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/add-organizations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/add-organizations.dto.ts new file mode 100644 index 000000000..bab205035 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/add-organizations.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { ArrayNotEmpty, ArrayUnique, IsArray, IsString, IsUUID } from 'class-validator'; + +export class AddOrganizationsDto { + @ApiProperty({ + example: ['32f54163-7166-48f1-93d8-ff217bdb0653'] + }) + @IsArray() + @ArrayNotEmpty({ message: 'Organization Ids array must not be empty' }) + @IsUUID('4', { each: true }) + @ArrayUnique({ message: 'Duplicate Organization Ids are not allowed' }) + @IsString({ each: true, message: 'Each organization Id in the array should be a string' }) + @Transform(({ value }) => value.map((item: string) => item.trim())) + organizationIds: string[]; + + ecosystemId: string; + + orgId: string; +} diff --git a/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts new file mode 100644 index 000000000..f2be132ff --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/create-ecosystem-dto.ts @@ -0,0 +1,50 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform, Type } from 'class-transformer'; +import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class CreateEcosystemDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Ecosystem name is required.' }) + @MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'Ecosystem name must be in string format.' }) + @IsNotSQLInjection({ message: 'Ecosystem name is required.' }) + name: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Description is required.' }) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description?: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'tag must be in string format.' }) + @Type(() => String) + tags?: string; + + userId: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo?: string; + + @ApiPropertyOptional({ example: 'false' }) + @IsBoolean() + @IsOptional() + @IsNotEmpty({ message: 'autoEndorsement should be boolean value' }) + autoEndorsement = false; + + orgId?: string; + } + \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts new file mode 100644 index 000000000..558736027 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/decline-endorsement-transaction.dto.ts @@ -0,0 +1,6 @@ +export class DeclienEndorsementTransactionDto { + ecosystemId: string; + orgId: string; + endorsementId: string; + +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts new file mode 100644 index 000000000..6166e3fa4 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/delete-invitations.dto.ts @@ -0,0 +1,19 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class deleteEcosystemInvitationsDto { + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @IsString({ message: 'Email should be a string' }) + @Transform(({ value }) => trim(value)) + email: string; + + ecosystemId: string; + invitationId: string; + orgId: string; + } + \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts new file mode 100644 index 000000000..a7011ee31 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/edit-ecosystem-dto.ts @@ -0,0 +1,49 @@ +import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +import { Transform } from 'class-transformer'; +import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class EditEcosystemDto { + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsNotEmpty({ message: 'Ecosystem name is required.' }) + @MinLength(2, { message: 'Ecosystem name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Ecosystem name must be at most 50 characters.' }) + @IsString({ message: 'Ecosystem name must be in string format.' }) + @IsNotSQLInjection({ message: 'Ecosystem name is required.' }) + name?: string; + + @ApiPropertyOptional() + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsNotEmpty({ message: 'Description is required.' }) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description?: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'tag must be in string format.' }) + tags? = ''; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'logo must be in string format.' }) + logo?: string; + + @ApiPropertyOptional({ example: 'false' }) + @IsBoolean() + @IsOptional() + @IsNotEmpty({ message: 'autoEndorsement should be boolean value' }) + autoEndorsement = false; + + userId?: string; + + } + \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts new file mode 100644 index 000000000..a0878bf15 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-endorsements.dto.ts @@ -0,0 +1,35 @@ + +import { Transform, Type } from 'class-transformer'; +import { toNumber } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { EndorserTransactionType } from '@credebl/enum/enum'; + +export class GetAllEndorsementsDto { + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageNumber = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + @Type(() => String) + search = ''; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => Number) + @Transform(({ value }) => toNumber(value)) + pageSize = 10; + + @ApiProperty({ + enum: [EndorserTransactionType.SCHEMA, EndorserTransactionType.CREDENTIAL_DEFINITION] + }) + @IsOptional() + @IsEnum(EndorserTransactionType) + type: EndorserTransactionType.SCHEMA | EndorserTransactionType.CREDENTIAL_DEFINITION; + +} diff --git a/apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts new file mode 100644 index 000000000..cbaa5b4d3 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-all-received-invitations.dto.ts @@ -0,0 +1,13 @@ + +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; +import { Invitation } from '@credebl/enum/enum'; +import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; + +export class GetAllSentEcosystemInvitationsDto extends PaginationDto { + + @ApiProperty({ required: false }) + @IsOptional() + @IsString() + status = Invitation.PENDING; +} diff --git a/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts b/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts new file mode 100644 index 000000000..f12af3ea4 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/get-members.dto.ts @@ -0,0 +1,27 @@ +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsOptional } from 'class-validator'; +import { SortMembers, SortValue } from '@credebl/enum/enum'; +import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; + +export class GetAllEcosystemMembersDto extends PaginationDto { + @ApiProperty({ + enum: [SortMembers.CREATED_DATE_TIME, SortMembers.ID, SortMembers.ORGANIZATION, SortMembers.STATUS], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortMembers) + sortField: string = SortMembers.CREATED_DATE_TIME; + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; +} diff --git a/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts new file mode 100644 index 000000000..03dbd03b5 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/request-schema.dto.ts @@ -0,0 +1,208 @@ +import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { ArrayMinSize, IsArray, IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { trim } from '@credebl/common/cast.helper'; +import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; + + +class AttributeValues { + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'attributeName is required' }) + attributeName: string; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'displayName is required' }) + displayName: string; + + @ApiProperty() + @IsBoolean() + @IsNotEmpty({ message: 'isRequired property is required' }) + isRequired: boolean; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'schemaDataType is required' }) + schemaDataType: string; + +} + +export class RequestIndySchemaDto { + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Schema name is required' }) + @IsString({ message: 'name must be in string format.' }) + schemaName: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Schema version is required' }) + @IsString({ message: 'version must be in string format.' }) + schemaVersion: string; + + @ApiProperty({ + type: [AttributeValues], + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name', + isRequired: true + } + ] + }) + @IsArray({ message: 'attributes must be an array' }) + @IsNotEmpty({ message: 'attributes are required' }) + @ArrayMinSize(1) + @ValidateNested({ each: true }) + @Type(() => AttributeValues) + attributes: AttributeValues[]; +} + +class W3CSchemaAttributesValue { + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'attributeName is required' }) + attributeName: string; + + @ApiProperty({ + description: 'The type of the schema', + enum: W3CSchemaDataType, + example: W3CSchemaDataType.STRING + }) + @IsEnum(W3CSchemaDataType, { message: 'Schema data type must be a valid type' }) + schemaDataType: W3CSchemaDataType; + + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'displayName is required' }) + displayName: string; + + @ApiProperty() + @IsBoolean() + @IsNotEmpty({ message: 'isRequired property is required' }) + isRequired: boolean; +} + +export class SchemaDetails { + @ApiProperty() + @IsString({ message: 'name must be a string.' }) + name: string; + + @ApiProperty() + @IsString({ message: 'version must be a string.' }) + version: string; + + @ApiProperty({ + example: ['name', 'id'] + }) + @IsArray({ message: 'attributes must be an array.' }) + @IsNotEmpty({ message: 'please provide valid attributes.' }) + attributes: string[]; + +} + +export class RequestW3CSchemaDto { + + @ApiProperty() + @IsString({ message: 'schemaName must be a string' }) + @Transform(({ value }) => value.trim()) + @IsNotEmpty({ message: 'schemaName is required' }) + schemaName: string; + + @ApiProperty() + @IsString({ message: 'description must be a string' }) + @IsNotEmpty({ message: 'description is required' }) + description: string; + + @ApiProperty({ + type: [W3CSchemaAttributesValue], + 'example': [ + { + attributeName: 'name', + schemaDataType: 'string', + displayName: 'Name', + isRequired: true + } + ] + }) + @ValidateNested({each: true}) + @Type(() => W3CSchemaAttributesValue) + @IsNotEmpty() + attributes: W3CSchemaAttributesValue []; + + @ApiProperty({ + description: 'The type of the schema', + enum: JSONSchemaType, + example: JSONSchemaType.POLYGON_W3C + }) + @IsEnum(JSONSchemaType, { message: 'Schema type must be a valid schema type' }) + @IsNotEmpty({ message: 'Type is required' }) + schemaType: JSONSchemaType; +} + +@ApiExtraModels(RequestIndySchemaDto, RequestW3CSchemaDto) +export class RequestSchemaDto { + + @ApiProperty() + @IsBoolean({ message: 'endorse property must be a boolean.' }) + @IsNotEmpty({ message: 'endorse property is required' }) + endorse?: boolean; + + @ApiProperty({ + description: 'The type of the schema', + enum: SchemaTypeEnum, + example: SchemaTypeEnum.INDY + }) + @IsEnum(SchemaTypeEnum, { message: 'Type must be a valid schema type' }) + @IsNotEmpty({ message: 'Type is required' }) + type: SchemaTypeEnum; + + @ApiProperty({ + type: Object, + oneOf: [{ $ref: getSchemaPath(RequestIndySchemaDto) }, { $ref: getSchemaPath(RequestW3CSchemaDto) }] + }) + @ValidateNested() + @Type(({ object }) => { + if (object.type === SchemaTypeEnum.INDY) { + return RequestIndySchemaDto; + } else if (object.type === SchemaTypeEnum.JSON) { + return RequestW3CSchemaDto; + } + }) + schemaPayload: RequestIndySchemaDto | RequestW3CSchemaDto; +} + +export class RequestCredDefDto { + @ApiProperty() + @IsBoolean({ message: 'endorse must be a boolean.' }) + @IsOptional() + endorse?: boolean; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'tag must be a string.' }) + tag: string; + + userId?: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsString({ message: 'schemaId must be a string.' }) + schemaId: string; + + @ApiProperty() + @ValidateNested() + @IsOptional() + @Type(() => SchemaDetails) + schemaDetails?: SchemaDetails; +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts b/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts new file mode 100644 index 000000000..2c2f7cd1c --- /dev/null +++ b/apps/api-gateway/src/ecosystem/dtos/send-invitation.dto.ts @@ -0,0 +1,26 @@ +import { ApiExtraModels, ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEmail, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +@ApiExtraModels() +export class EcosystemInvitationDto { + + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @IsString({ message: 'Email should be a string' }) + @Transform(({ value }) => trim(value)) + email: string; + +} +@ApiExtraModels() +export class BulkEcosystemInvitationDto { + + @ApiProperty({ type: [EcosystemInvitationDto] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => EcosystemInvitationDto) + invitations: EcosystemInvitationDto[]; + ecosystemId: string; +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.controller.ts b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts new file mode 100644 index 000000000..0c90cee11 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.controller.ts @@ -0,0 +1,706 @@ +import { ApiBearerAuth, ApiExcludeEndpoint, ApiExtraModels, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { EcosystemService } from './ecosystem.service'; +import { Controller, UseFilters, Put, Post, Get, Body, Param, UseGuards, Query, BadRequestException, Delete, HttpStatus, Res, ParseUUIDPipe } from '@nestjs/common'; +import { RequestCredDefDto, RequestSchemaDto, RequestW3CSchemaDto } from './dtos/request-schema.dto'; +import IResponse from '@credebl/common/interfaces/response.interface'; +import { Response } from 'express'; +import { ApiResponseDto } from '../dtos/apiResponse.dto'; +import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; +import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; +import { AuthGuard } from '@nestjs/passport'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-received-invitations.dto'; +import { EcosystemRoles, Invitation } from '@credebl/enum/enum'; +import { User } from '../authz/decorators/user.decorator'; +import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { user } from '@prisma/client'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-invitations.dto'; +import { EcosystemRolesGuard } from '../authz/guards/ecosystem-roles.guard'; +import { EcosystemsRoles, Roles } from '../authz/decorators/roles.decorator'; +import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; +import { IEcosystemInvitations, IEditEcosystem, IEndorsementTransaction } from 'apps/ecosystem/interfaces/ecosystem.interfaces'; +import { AddOrganizationsDto } from './dtos/add-organizations.dto'; +import { TrimStringParamPipe } from '@credebl/common/cast.helper'; + + +@UseFilters(CustomExceptionFilter) +@Controller('ecosystem') +@ApiTags('ecosystem') +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) +export class EcosystemController { + constructor( + private readonly ecosystemService: EcosystemService + ) { } + + @Get('/:ecosystemId/:orgId/endorsement-transactions') + @ApiOperation({ summary: 'Get all endorsement transactions', description: 'Get all endorsement transactions' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEndorsementTranasactions( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getAllEndorsementsDto: GetAllEndorsementsDto, + @Res() res: Response + ): Promise { + + const ecosystemList = await this.ecosystemService.getEndorsementTranasactions(ecosystemId, orgId, getAllEndorsementsDto); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.fetchEndorsors, + data: ecosystemList.response + }; + return res.status(200).json(finalResponse); + } + + @Get('/:ecosystemId/:orgId/schemas') + @ApiOperation({ summary: 'Get all ecosystem schemas', description: 'Get all ecosystem schemas' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + async getAllEcosystemSchemas( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() paginationDto: PaginationDto, + @Res() res: Response + ): Promise { + + const schemaList = await this.ecosystemService.getAllEcosystemSchemas(ecosystemId, orgId, paginationDto); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.allschema, + data: schemaList + }; + return res.status(200).json(finalResponse); + } + + /** + * @returns Ecosystem details + */ + @Get('/:orgId') + @ApiOperation({ summary: 'Get all organization ecosystems', description: 'Get all existing ecosystems of an specific organization' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @ApiBearerAuth() + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEcosystem( + @Query() paginationDto: PaginationDto, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, + @Res() res: Response + ): Promise { + const ecosystemList = await this.ecosystemService.getAllEcosystem(orgId, paginationDto); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.fetch, + data: ecosystemList + }; + return res.status(200).json(finalResponse); + } + + /** + * @param ecosystemId + * @param orgId + * @returns Ecosystem dashboard details + */ + + @Get('/:ecosystemId/:orgId/dashboard') + @ApiOperation({ summary: 'Get ecosystem dashboard details', description: 'Get ecosystem dashboard details' }) + @ApiResponse({ status: 200, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @ApiBearerAuth() + async getEcosystemDashboardDetails(@Param('ecosystemId') ecosystemId: string, @Param('orgId') orgId: string, @Res() res: Response): Promise { + + const getEcosystemDetails = await this.ecosystemService.getEcosystemDashboardDetails(ecosystemId, orgId); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.getEcosystemDashboard, + data: getEcosystemDetails + }; + return res.status(200).json(finalResponse); + + } + + @Get('/:orgId/users/invitations') + @ApiOperation({ summary: 'Get received ecosystem invitations', description: 'Get received ecosystem invitations' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiBearerAuth() + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + @ApiQuery({ + name: 'status', + type: String, + required: false + }) + async getEcosystemInvitations( + @Query() getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, + @Param('orgId') orgId: string, + @User() user: user, @Res() res: Response): Promise { + + if (!Object.values(Invitation).includes(getAllInvitationsDto.status)) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidInvitationStatus); + } + const getEcosystemInvitation = await this.ecosystemService.getEcosystemInvitations(getAllInvitationsDto, user.email, getAllInvitationsDto.status); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.getInvitation, + data: getEcosystemInvitation + }; + return res.status(200).json(finalResponse); + + } + + @Get('/:ecosystemId/:orgId/invitations') + @ApiOperation({ summary: 'Get all sent invitations', description: 'Get all sent invitations' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getInvitationsByEcosystemId( + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @Query() paginationDto: PaginationDto, + @User() user: user, + @Res() res: Response): Promise { + + const getInvitationById = await this.ecosystemService.getInvitationsByEcosystemId(ecosystemId, paginationDto, String(user.id)); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.getInvitation, + data: getInvitationById + }; + return res.status(200).json(finalResponse); + + } + + /** + * + * @param res + * @returns Ecosystem members list + */ + @Get('/:ecosystemId/:orgId/members') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Get ecosystem members list', description: 'Get ecosystem members list.' }) + @ApiQuery({ + name: 'pageNumber', + type: Number, + required: false + }) + @ApiQuery({ + name: 'pageSize', + type: Number, + required: false + }) + @ApiQuery({ + name: 'search', + type: String, + required: false + }) + async getEcosystemMembers( + @Param('ecosystemId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for ecosystemId`); }})) ecosystemId: string, + @Param('orgId') orgId: string, + @Query() getEcosystemMembers: GetAllEcosystemMembersDto, + @Res() res: Response): Promise { + + const members = await this.ecosystemService.getEcosystemMembers(ecosystemId, getEcosystemMembers); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.fetchMembers, + data: members + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + + + @Post('/:ecosystemId/:orgId/transaction/schema') + @ApiExtraModels(RequestSchemaDto, RequestW3CSchemaDto) + @ApiOperation({ summary: 'Request new schema', description: 'Create request for new schema' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER, EcosystemRoles.ECOSYSTEM_LEAD, EcosystemRoles.ECOSYSTEM_OWNER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async requestSchemaTransaction( + @Body() requestSchemaPayload: RequestSchemaDto, + @Param( + 'orgId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) orgId: string, + @Param('ecosystemId', TrimStringParamPipe) ecosystemId: string, + @Res() res: Response, + @User() user: user + ): Promise { + + const createSchemaRequest = await this.ecosystemService.schemaEndorsementRequest( + requestSchemaPayload, + user, + orgId, + ecosystemId + ); + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.schemaRequest, + data: createSchemaRequest + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + + /** + * @param createOrgDto + * @param res + * @returns Created Ecosystem details + */ + @Post('/:orgId') + @ApiOperation({ summary: 'Create a new ecosystem', description: 'Create a new ecosystem' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER) + async createNewEcosystem( + @Body() createOrgDto: CreateEcosystemDto, + @Param('orgId') orgId: string, + @User() user: user, + @Res() res: Response): Promise { + createOrgDto.orgId = orgId; + createOrgDto.userId = user.id; + const createEcosystemResponse = await this.ecosystemService.createEcosystem(createOrgDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.create, + data: createEcosystemResponse + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + + @Post('/:ecosystemId/:orgId/transaction/cred-def') + @ApiOperation({ summary: 'Request new credential-definition', description: 'Request new credential-definition' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async requestCredDefTransaction(@Body() requestCredDefPayload: RequestCredDefDto, @Param('ecosystemId', TrimStringParamPipe) ecosystemId: string, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response, @User() user: user): Promise { + requestCredDefPayload.userId = user.id; + const createCredDefRequest: IEndorsementTransaction = await this.ecosystemService.credDefEndorsementRequest(requestCredDefPayload, orgId, ecosystemId); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.credDefRequest, + data: createCredDefRequest + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Post('/:ecosystemId/:orgId/transaction/sign/:endorsementId') + @ApiOperation({ summary: 'Sign transaction', description: 'Sign transaction' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async SignEndorsementRequests( + @Param('endorsementId', TrimStringParamPipe) endorsementId: string, + @Param('ecosystemId', TrimStringParamPipe) ecosystemId: string, + @Param( + 'orgId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) orgId: string, + @Res() res: Response + ): Promise { + + const transactionResponse = await this.ecosystemService.signTransaction(endorsementId, ecosystemId); + + const responseMessage = + true === transactionResponse['autoEndorsement'] + ? ResponseMessages.ecosystem.success.AutoSignAndSubmit + : ResponseMessages.ecosystem.success.sign; + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: responseMessage, + data: transactionResponse + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + @Post('/:ecosystemId/:orgId/transaction/submit/:endorsementId') + @ApiOperation({ summary: 'Submit transaction', description: 'Submit transaction' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_MEMBER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + async SubmitEndorsementRequests( + @Param('endorsementId', TrimStringParamPipe) endorsementId: string, + @Param('ecosystemId', TrimStringParamPipe) ecosystemId: string, + @Param( + 'orgId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) + orgId: string, + @User() user: user, + @Res() res: Response + ): Promise { + const transactionResponse = await this.ecosystemService.submitTransaction(endorsementId, ecosystemId, orgId, user); + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: transactionResponse?.['responseMessage'], + data: transactionResponse?.['txnPayload'] + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } + + + /** + * + * @param bulkInvitationDto + * @param ecosystemId + * @param user + * @param res + * @returns Ecosystem invitation send details + */ + @Post('/:ecosystemId/:orgId/invitations') + @ApiOperation({ + summary: 'Send ecosystem invitation', + description: 'Send ecosystem invitation' + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async createInvitation(@Body() bulkInvitationDto: BulkEcosystemInvitationDto, + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @User() user: user, @Res() res: Response): Promise { + + bulkInvitationDto.ecosystemId = ecosystemId; + + const ecosystemInvitationResponse: IEcosystemInvitations[] = await this.ecosystemService.createInvitation(bulkInvitationDto, user.id, user.email, orgId); + + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.createInvitation, + data: ecosystemInvitationResponse + }; + + return res.status(HttpStatus.CREATED).json(finalResponse); + + } + + /** + * + * @param orgId + * @param ecosystemId + */ + @Post('/:ecosystemId/:orgId/orgs') + @ApiOperation({ + summary: 'Add multiple organizations of ecosystem owner in ecosystem', + description: 'Add multiple organizations of ecosystem owner under the same ecosystem' + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async addOrganizationsInEcosystem( + @Body() addOrganizationsDto: AddOrganizationsDto, + @Param('ecosystemId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.ecosystem.error.invalidEcosystemId); }}), TrimStringParamPipe) ecosystemId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @User() user: user, + @Res() res: Response + ): Promise { + + addOrganizationsDto.ecosystemId = ecosystemId; + addOrganizationsDto.orgId = orgId; + + const addOrganizations = await this.ecosystemService.addOrganizationsInEcosystem(addOrganizationsDto, user.id); + const { results, statusCode, message } = addOrganizations; + + const finalResponse: IResponse = { + statusCode, + message, + data: results + }; + + return res.status(statusCode).json(finalResponse); + } + + + /** + * + * @param res + * @returns + */ + @Put('transaction/endorsement/auto') + // Not required anywhere + @ApiExcludeEndpoint() + @ApiOperation({ + summary: 'Auto sign and submit transactions', + description: 'Auto sign and submit transactions' + }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async autoSignAndSubmitTransaction( + @Res() res: Response + ): Promise { + await this.ecosystemService.autoSignAndSubmitTransaction(); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.AutoEndorsementTransaction + }; + return res.status(200).json(finalResponse); + } + + /** + * + * @param acceptRejectEcosystemInvitation + * @param reqUser + * @param res + * @returns Ecosystem invitation status + */ + @Put('/:orgId/invitations/:invitationId') + @ApiOperation({ + summary: 'Accept or reject ecosystem invitation', + description: 'Accept or Reject ecosystem invitations' + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER) + async acceptRejectEcosystemInvitaion(@Body() acceptRejectEcosystemInvitation: AcceptRejectEcosystemInvitationDto, @Param('orgId') orgId: string, @Param('invitationId') invitationId: string, @User() user: user, @Res() res: Response): Promise { + acceptRejectEcosystemInvitation.orgId = orgId; + acceptRejectEcosystemInvitation.invitationId = invitationId; + acceptRejectEcosystemInvitation.userId = user.id; + const invitationRes = await this.ecosystemService.acceptRejectEcosystemInvitaion(acceptRejectEcosystemInvitation, user.email); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: invitationRes.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + /** + * + * @param editEcosystemDto + * @param ecosystemId + * @param orgId + * @returns Response with edited details of ecosystem + */ + + + @Put('/:ecosystemId/:orgId') + @ApiOperation({ summary: 'Edit ecosystem', description: 'Edit ecosystem' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard, EcosystemRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER) + async editEcosystem( + @Body() editEcosystemDto: EditEcosystemDto, + @Param('ecosystemId') ecosystemId: string, + @Param('orgId') orgId: string, + @User() user: user, + @Res() res: Response): Promise { + editEcosystemDto.userId = user.id; + const editEcosystemResponse: IEditEcosystem = await this.ecosystemService.editEcosystem(editEcosystemDto, ecosystemId); + const finalResponse: IResponse = { + statusCode: 200, + message: ResponseMessages.ecosystem.success.update, + data: editEcosystemResponse + }; + return res.status(200).json(finalResponse); + } + + + /** + * + * @param declineEndorsementTransactionRequest + * + * @param res + * @returns endorsement transaction status + */ + @Put('/:ecosystemId/:orgId/transactions/:endorsementId') + @ApiOperation({ + summary: 'Decline Endorsement Request By Lead', + description: 'Decline Endorsement Request By Lead' + }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @ApiBearerAuth() + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + async declineEndorsementRequestByLead( + @Param('ecosystemId') ecosystemId: string, + @Param('endorsementId') endorsementId: string, + @Param('orgId') orgId: string, + @Res() res: Response + ): Promise { + const response = await this.ecosystemService.declineEndorsementRequestByLead(ecosystemId, endorsementId, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.DeclineEndorsementTransaction, + data: response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + @Delete('/:orgId/member-org') + @ApiOperation({ summary: 'Delete organization from ecosystem as a ecosystem member', description: 'Delete organization from ecosystem as a ecosystem member' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER) + @ApiBearerAuth() + async deleteOrgFromEcosystem( + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Res() res: Response, + @User() user: user + ): Promise { + + await this.ecosystemService.deleteOrgFromEcosystem(orgId, user); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.deleteEcosystemMember + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + + @Delete('/:ecosystemId/:orgId/invitations/:invitationId') + @ApiOperation({ summary: 'Delete ecosystem pending invitations', description: 'Delete ecosystem pending invitations' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt'), EcosystemRolesGuard, OrgRolesGuard) + @EcosystemsRoles(EcosystemRoles.ECOSYSTEM_OWNER, EcosystemRoles.ECOSYSTEM_LEAD) + @Roles(OrgRoles.OWNER) + @ApiBearerAuth() + async deleteEcosystemInvitations( + @Param('ecosystemId') ecosystemId: string, + @Param('invitationId') invitationId: string, + @Param('orgId') orgId: string, + @Res() res: Response): Promise { + + const deletedEcosystemInvitationResponse = await this.ecosystemService.deleteEcosystemInvitations(invitationId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.ecosystem.success.delete, + data:deletedEcosystemInvitationResponse + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + + +} \ No newline at end of file diff --git a/apps/api-gateway/src/ecosystem/ecosystem.module.ts b/apps/api-gateway/src/ecosystem/ecosystem.module.ts new file mode 100644 index 000000000..1a25f8ea0 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.module.ts @@ -0,0 +1,29 @@ +import { CommonModule, CommonService } from '@credebl/common'; + +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { CommonConstants } from '@credebl/common/common.constant'; + +@Module({ + imports: [ + HttpModule, + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: getNatsOptions(CommonConstants.ECOSYSTEM_SERVICE, process.env.API_GATEWAY_NKEY_SEED) + }, + CommonModule + ]) + ], + controllers: [EcosystemController], + providers: [EcosystemService, CommonService] +}) +export class EcosystemModule { } + diff --git a/apps/api-gateway/src/ecosystem/ecosystem.service.ts b/apps/api-gateway/src/ecosystem/ecosystem.service.ts new file mode 100644 index 000000000..3e14a6758 --- /dev/null +++ b/apps/api-gateway/src/ecosystem/ecosystem.service.ts @@ -0,0 +1,214 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from 'libs/service/base.service'; +import { BulkEcosystemInvitationDto } from './dtos/send-invitation.dto'; +import { AcceptRejectEcosystemInvitationDto } from './dtos/accept-reject-invitations.dto'; +import { GetAllSentEcosystemInvitationsDto } from './dtos/get-all-received-invitations.dto'; +import { GetAllEcosystemMembersDto } from './dtos/get-members.dto'; +import { GetAllEndorsementsDto } from './dtos/get-all-endorsements.dto'; + +import { RequestSchemaDto, RequestCredDefDto} from './dtos/request-schema.dto'; +import { CreateEcosystemDto } from './dtos/create-ecosystem-dto'; +import { EditEcosystemDto } from './dtos/edit-ecosystem-dto'; +import { IEcosystemDashboard, IEcosystemInvitation, IEcosystemInvitations, IEcosystem, IEditEcosystem, IEndorsementTransaction, ISchemaResponse } from 'apps/ecosystem/interfaces/ecosystem.interfaces'; +import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { AddOrganizationsDto } from './dtos/add-organizations.dto'; +import { user } from '@prisma/client'; + +@Injectable() +export class EcosystemService extends BaseService { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { + super('EcosystemService'); + } + + /** + * + * @param createEcosystemDto + * @returns Ecosystem creation success + */ + async createEcosystem(createEcosystemDto: CreateEcosystemDto): Promise { + const payload = { createEcosystemDto }; + return this.sendNatsMessage(this.serviceProxy, 'create-ecosystem', payload); + } + + /** + * + * @param editEcosystemDto + * @returns Ecosystem creation success + */ + async editEcosystem(editEcosystemDto: EditEcosystemDto, ecosystemId: string): Promise { + const payload = { editEcosystemDto, ecosystemId }; + return this.sendNatsMessage(this.serviceProxy, 'edit-ecosystem', payload); + } + + /** + * + * + * @returns Get all ecosystems + */ + async getAllEcosystem(orgId: string, payload: PaginationDto): Promise { + payload['orgId'] = orgId; + return this.sendNatsMessage(this.serviceProxy, 'get-all-ecosystem', payload); + } + + /** + * + * + * @returns Get ecosystems dashboard card counts + */ + async getEcosystemDashboardDetails(ecosystemId: string, orgId: string): Promise { + const payload = { ecosystemId, orgId }; + return this.sendNatsMessage(this.serviceProxy, 'get-ecosystem-dashboard-details', payload); + } + + /** + * + * @param bulkInvitationDto + * @param userId + * @returns + */ + async createInvitation( + bulkInvitationDto: BulkEcosystemInvitationDto, + userId: string, + userEmail: string, + orgId: string + ): Promise { + const payload = { bulkInvitationDto, userId, userEmail, orgId }; + + return this.sendNatsMessage(this.serviceProxy, 'send-ecosystem-invitation', payload); + } + + /** + * + * @param orgId + * @param ecosystemId + */ + async addOrganizationsInEcosystem(addOrganizationsDto: AddOrganizationsDto, userId: string): Promise<{ results: { statusCode: number, message: string, error?: string, data?: { orgId: string } }[], statusCode: number, message: string }> { + const payload = { ...addOrganizationsDto, userId }; + return this.sendNatsMessage(this.serviceProxy, 'add-organization-in-ecosystem', payload); + } + + async getInvitationsByEcosystemId( + ecosystemId: string, + paginationDto: PaginationDto, + userId: string + ): Promise { + const { pageNumber, pageSize, search } = paginationDto; + const payload = { ecosystemId, pageNumber, pageSize, search, userId }; + return this.sendNatsMessage(this.serviceProxy, 'get-sent-invitations-ecosystemId', payload); + } + + /** + * + * @returns Ecosystem members + */ + async getEcosystemMembers( + ecosystemId: string, + payload: GetAllEcosystemMembersDto + ): Promise<{ response: object }> { + payload['ecosystemId'] = ecosystemId; + return this.sendNatsMessage(this.serviceProxy, 'fetch-ecosystem-members', payload); + } + + /** + * + * @returns Ecosystem Invitations details + */ + async getEcosystemInvitations( + getAllInvitationsDto: GetAllSentEcosystemInvitationsDto, + userEmail: string, + status: string + ): Promise { + const { pageNumber, pageSize, search } = getAllInvitationsDto; + const payload = { userEmail, status, pageNumber, pageSize, search }; + return this.sendNatsMessage(this.serviceProxy, 'get-ecosystem-invitations', payload); + } + + async deleteEcosystemInvitations(invitationId: string): Promise { + const payload = { invitationId }; + return this.sendNats(this.serviceProxy, 'delete-ecosystem-invitations', payload); + } + + async acceptRejectEcosystemInvitaion( + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + userEmail: string + ): Promise<{ response: string }> { + const payload = { acceptRejectInvitation, userEmail }; + return this.sendNats(this.serviceProxy, 'accept-reject-ecosystem-invitations', payload); + } + + async fetchEcosystemOrg(ecosystemId: string, orgId: string): Promise { + const payload = { ecosystemId, orgId }; + return this.sendNatsMessage(this.serviceProxy, 'fetch-ecosystem-org-data', payload); + } + + async getEndorsementTranasactions( + ecosystemId: string, + orgId: string, + getAllEndorsements: GetAllEndorsementsDto + ): Promise<{ response: object }> { + const { pageNumber, pageSize, search, type } = getAllEndorsements; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search, type }; + return this.sendNats(this.serviceProxy, 'get-endorsement-transactions', payload); + } + + async getAllEcosystemSchemas( + ecosystemId: string, + orgId: string, + paginationDto: PaginationDto + ): Promise { + const { pageNumber, pageSize, search } = paginationDto; + const payload = { ecosystemId, orgId, pageNumber, pageSize, search }; + return this.sendNatsMessage(this.serviceProxy, 'get-all-ecosystem-schemas', payload); + } + + async schemaEndorsementRequest( + requestSchemaPayload: RequestSchemaDto, + user: user, + orgId: string, + ecosystemId: string + ): Promise { + const payload = { requestSchemaPayload, user, orgId, ecosystemId }; + return this.sendNatsMessage(this.serviceProxy, 'schema-endorsement-request', payload); + } + + async credDefEndorsementRequest( + requestCredDefPayload: RequestCredDefDto, + orgId: string, + ecosystemId: string + ): Promise { + const payload = { requestCredDefPayload, orgId, ecosystemId }; + return this.sendNatsMessage(this.serviceProxy, 'credDef-endorsement-request', payload); + } + + async signTransaction(endorsementId: string, ecosystemId: string): Promise { + const payload = { endorsementId, ecosystemId }; + return this.sendNatsMessage(this.serviceProxy, 'sign-endorsement-transaction', payload); + } + + async submitTransaction(endorsementId: string, ecosystemId: string, orgId: string, user: user): Promise { + const payload = { endorsementId, ecosystemId, orgId, user }; + return this.sendNatsMessage(this.serviceProxy, 'submit-endorsement-transaction', payload); + } + + async autoSignAndSubmitTransaction(): Promise<{ response: object }> { + const payload = {}; + return this.sendNats(this.serviceProxy, 'auto-endorsement-transaction', payload); + } + + async declineEndorsementRequestByLead( + ecosystemId: string, + endorsementId: string, + orgId: string + ): Promise<{ response: object }> { + const payload = { ecosystemId, endorsementId, orgId }; + return this.sendNatsMessage(this.serviceProxy, 'decline-endorsement-transaction', payload); + } + + async deleteOrgFromEcosystem(orgId: string, userDetails: user): Promise { + const payload = { orgId, userDetails }; + return this.sendNats(this.serviceProxy, 'delete-org-from-ecosystem', payload); + } + +} \ No newline at end of file diff --git a/apps/api-gateway/src/fido/fido.controller.ts b/apps/api-gateway/src/fido/fido.controller.ts index 7b6695e2e..75e19f041 100644 --- a/apps/api-gateway/src/fido/fido.controller.ts +++ b/apps/api-gateway/src/fido/fido.controller.ts @@ -1,39 +1,10 @@ -import { - Body, - Controller, - Delete, - Get, - HttpStatus, - Logger, - Param, - Post, - Put, - Query, - Request, - Res, - UseFilters -} from '@nestjs/common'; -import { - ApiBadRequestResponse, - ApiBearerAuth, - ApiExcludeEndpoint, - ApiForbiddenResponse, - ApiOperation, - ApiQuery, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Param, Post, Put, Query, Request, Res, UseFilters } from '@nestjs/common'; +import { ApiBadRequestResponse, ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { BadRequestErrorDto } from '../dtos/bad-request-error.dto'; -import { - GenerateAuthenticationDto, - GenerateRegistrationDto, - UpdateFidoUserDetailsDto, - VerifyRegistrationDto, - VerifyAuthenticationDto -} from '../dtos/fido-user.dto'; +import { GenerateAuthenticationDto, GenerateRegistrationDto, UpdateFidoUserDetailsDto, VerifyRegistrationDto, VerifyAuthenticationDto } from '../dtos/fido-user.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; +import { InternalServerErrorDto } from '../dtos/internal-server-error-res.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { FidoService } from './fido.service'; import { ResponseMessages } from '@credebl/common/response-messages'; @@ -46,251 +17,208 @@ import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler @UseFilters(CustomExceptionFilter) @Controller('auth') @ApiTags('fido') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) -@ApiBadRequestResponse({ description: 'Bad Request', type: BadRequestErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) export class FidoController { - private logger = new Logger('FidoController'); - constructor(private readonly fidoService: FidoService) {} + private logger = new Logger('FidoController'); + constructor(private readonly fidoService: FidoService) { } - /** - * Fetch fido user details - * @param email The email of the user - * @param res The response object - * @returns User details - */ - @Get('/passkey/:email') - // TODO: Check if roles are required here? - // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiBearerAuth() - @ApiOperation({ - summary: 'Fetch fido user details', - description: 'Retrieve the details of a FIDO user by their email address.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBadRequestResponse({ description: 'Bad Request', type: BadRequestErrorDto }) - async fetchFidoUserDetails(@Request() req, @Param('email') email: string, @Res() res: Response): Promise { - try { - const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(email.toLowerCase()); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.fetchUsers, - data: fidoUserDetails.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; + /** + * + * @param userName + * @param res + * @returns User get success + */ + @Get('/passkey/:email') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @ApiResponse({ + status: 500, + description: 'Internal server error', + type: InternalServerErrorDto + }) + + @ApiOperation({ summary: 'Fetch fido user details' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBadRequestResponse({ status: 400, description: 'Bad Request', type: BadRequestErrorDto }) + async fetchFidoUserDetails(@Request() req, @Param('email') email: string, @Res() res: Response): Promise { + try { + const fidoUserDetails = await this.fidoService.fetchFidoUserDetails(req.params.email.toLowerCase()); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchUsers, + data: fidoUserDetails.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } catch (error) { + this.logger.error(`Error::${error}`); + throw error; + } + } + + /** + * + * @param GenerateRegistrationDto + * @param res + * @returns Generate registration response + */ + @Post('/passkey/generate-registration/:email') + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Generate registration option' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async generateRegistrationOption(@Body() body: GenerateRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + try { + const { deviceFlag } = body; + const registrationOption = await this.fidoService.generateRegistrationOption(deviceFlag, email.toLowerCase()); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.fido.success.RegistrationOption, + data: registrationOption.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } catch (error) { + this.logger.error(`Error::${error}`); + } } - } - /** - * Generate registration option - * @param GenerateRegistrationDto The registration details - * @param email The email of the user - * @param res The response object - * @returns Registration options + + /** + * + * @param VerifyRegistrationDto + * @param res + * @returns User create success */ - @Post('/passkey/generate-registration/:email') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: 'Generate registration option', - description: 'Generate registration options for a FIDO user.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async generateRegistrationOption( - @Body() body: GenerateRegistrationDto, - @Param('email') email: string, - @Res() res: Response - ): Promise { - try { - const { deviceFlag } = body; - const registrationOption = await this.fidoService.generateRegistrationOption(deviceFlag, email.toLowerCase()); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.fido.success.RegistrationOption, - data: registrationOption.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; + @Post('/passkey/verify-registration/:email') + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Verify registration' }) + async verifyRegistration(@Request() req, @Body() verifyRegistrationDto: VerifyRegistrationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, req.params.email.toLowerCase()); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.verifyRegistration, + data: verifyRegistration.response + }; + return res.status(HttpStatus.OK).json(finalResponse); } - } - /** - * Verify registration - * @param verifyRegistrationDto The registration verification details - * @param email The email of the user - * @param res The response object - * @returns Verification result - */ - @Post('/passkey/verify-registration/:email') - @ApiExcludeEndpoint() - @ApiOperation({ summary: 'Verify registration', description: 'Verify the registration of a FIDO user.' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async verifyRegistration( - @Request() req, - @Body() verifyRegistrationDto: VerifyRegistrationDto, - @Param('email') email: string, - @Res() res: Response - ): Promise { - const verifyRegistration = await this.fidoService.verifyRegistration(verifyRegistrationDto, email.toLowerCase()); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.verifyRegistration, - data: verifyRegistration.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * + * @param GenerateAuthenticationDto + * @param res + * @returns Generate authentication object + */ + @Post('/passkey/authentication-options') + @ApiOperation({ summary: 'Generate authentication option' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async generateAuthenticationOption(@Body() body: GenerateAuthenticationDto, @Request() req, @Res() res: Response): Promise { - /** - * Generate authentication option - * @param GenerateAuthenticationDto The authentication details - * @param res The response object - * @returns Authentication options - */ - @Post('/passkey/authentication-options') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: 'Generate authentication option', - description: 'Generate authentication options for a FIDO user.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async generateAuthenticationOption( - @Body() body: GenerateAuthenticationDto, - @Request() req, - @Res() res: Response - ): Promise { - const generateAuthentication = await this.fidoService.generateAuthenticationOption(body); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.generateAuthenticationOption, - data: generateAuthentication.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + const generateAuthentication = await this.fidoService.generateAuthenticationOption(body); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.generateAuthenticationOption, + data: generateAuthentication.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Verify authentication - * @param verifyAuthenticationDto The authentication verification details - * @param email The email of the user - * @param res The response object - * @returns Verification result + /** + * + * @param verifyAuthenticationDto + * @param res + * @returns Verify authentication object */ - @Post('/passkey/verify-authentication/:email') - @ApiExcludeEndpoint() - @ApiOperation({ summary: 'Verify authentication', description: 'Verify the authentication of a FIDO user.' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async verifyAuthentication( - @Request() req, - @Body() verifyAuthenticationDto: VerifyAuthenticationDto, - @Param('email') email: string, - @Res() res: Response - ): Promise { - const verifyAuthentication = await this.fidoService.verifyAuthentication( - verifyAuthenticationDto, - email.toLowerCase() - ); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.login, - data: verifyAuthentication.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + @Post('/passkey/verify-authentication/:email') + @ApiOperation({ summary: 'Verify authentication' }) + async verifyAuthentication(@Request() req, @Body() verifyAuthenticationDto: VerifyAuthenticationDto, @Param('email') email: string, @Res() res: Response): Promise { + const verifyAuthentication = await this.fidoService.verifyAuthentication(verifyAuthenticationDto, req.params.email.toLowerCase()); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.login, + data: verifyAuthentication.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Update fido user details - * @param updateFidoUserDetailsDto The user details to be updated - * @param credentialId The credential ID of the user - * @param res The response object - * @returns Updated user details - */ - @Put('/passkey/user-details/:credentialId') - @ApiExcludeEndpoint() - @ApiOperation({ summary: 'Update fido user details', description: 'Update the details of a FIDO user.' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async updateFidoUser( - @Request() req, - @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, - @Param('credentialId') credentialId: string, - @Res() res: Response - ): Promise { - const verifyRegistration = await this.fidoService.updateFidoUser( - updateFidoUserDetailsDto, - decodeURIComponent(credentialId) - ); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateUserDetails, - data: verifyRegistration.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } +/** + * + * @param updateFidoUserDetailsDto + * @param res + * @returns User update success + */ + @Put('/passkey/user-details/:credentialId') + @ApiExcludeEndpoint() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Update fido user details' }) + async updateFidoUser(@Request() req, @Body() updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, @Param('credentialId') credentialId: string, @Res() res: Response): Promise { + const verifyRegistration = await this.fidoService.updateFidoUser(updateFidoUserDetailsDto, decodeURIComponent(credentialId)); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.updateUserDetails, + data: verifyRegistration.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } + - /** - * Update fido user device name - * @param credentialId The credential ID of the user - * @param deviceName The new device name - * @param res The response object - * @returns Updated device name - */ - @Put('/passkey/:credentialId') - @ApiBearerAuth() - // TODO: Check if roles are required here? - // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiOperation({ summary: 'Update fido user device name', description: 'Update the device name of a FIDO user.' }) - @ApiQuery({ name: 'deviceName', required: true }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async updateFidoUserDeviceName( - @Param('credentialId') credentialId: string, - @Query('deviceName') deviceName: string, - @Res() res: Response - ): Promise { - try { - const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.updateDeviceName, - data: updateDeviceName.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; + @Put('/passkey/:credentialId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @ApiResponse({ + status: 500, + description: 'Internal server error', + type: InternalServerErrorDto + }) + @ApiQuery( + { name: 'deviceName', required: true } + ) + @ApiOperation({ summary: 'Update fido user device name' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async updateFidoUserDeviceName(@Param('credentialId') credentialId: string, @Query('deviceName') deviceName: string, @Res() res: Response): Promise { + try { + const updateDeviceName = await this.fidoService.updateFidoUserDeviceName(credentialId, deviceName); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.updateDeviceName, + data: updateDeviceName.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + } catch (error) { + this.logger.error(`Error::${error}`); + throw error; + } } - } - /** - * Delete fido user device - * @param credentialId The credential ID of the user - * @param res The response object - * @returns Success message - */ - @Delete('/passkey/:credentialId') - @ApiBearerAuth() - // TODO: Check if roles are required here? - // @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) - @ApiOperation({ summary: 'Delete fido user device', description: 'Delete a FIDO user device by its credential ID.' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async deleteFidoUserDevice(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { - try { - const deleteFidoUser = await this.fidoService.deleteFidoUserDevice(credentialId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.fido.success.deleteDevice, - data: deleteFidoUser.response - }; - return res.status(HttpStatus.OK).json(finalResponse); - } catch (error) { - this.logger.error(`Error::${error}`); - throw error; + @Delete('/passkey/:credentialId') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @ApiBearerAuth() + @ApiResponse({ + status: 500, + description: 'Internal server error', + type: InternalServerErrorDto + }) + @ApiQuery( + { name: 'credentialId', required: true } + ) + @ApiOperation({ summary: 'Delete fido user device' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async deleteFidoUserDevice(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { + try { + const deleteFidoUser = await this.fidoService.deleteFidoUserDevice(credentialId); + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.fido.success.deleteDevice, + data: deleteFidoUser.response + }; + return res.status(HttpStatus.OK).json(finalResponse); + + } catch (error) { + this.logger.error(`Error::${error}`); + throw error; + } } - } + } diff --git a/apps/api-gateway/src/fido/fido.module.ts b/apps/api-gateway/src/fido/fido.module.ts index 6f05ce04d..7805e7cbb 100644 --- a/apps/api-gateway/src/fido/fido.module.ts +++ b/apps/api-gateway/src/fido/fido.module.ts @@ -4,7 +4,6 @@ import { FidoController } from './fido.controller'; import { FidoService } from './fido.service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports:[ @@ -18,6 +17,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [FidoController], - providers: [FidoService, NATSClient] + providers: [FidoService] }) export class FidoModule { } diff --git a/apps/api-gateway/src/fido/fido.service.ts b/apps/api-gateway/src/fido/fido.service.ts index 0b131bbbb..6fae79138 100644 --- a/apps/api-gateway/src/fido/fido.service.ts +++ b/apps/api-gateway/src/fido/fido.service.ts @@ -1,70 +1,59 @@ import { Inject, Injectable } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; -import { - UpdateFidoUserDetailsDto, - VerifyRegistrationDto, - GenerateAuthenticationDto, - VerifyAuthenticationDto -} from '../dtos/fido-user.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; +import { UpdateFidoUserDetailsDto, VerifyRegistrationDto, GenerateAuthenticationDto, VerifyAuthenticationDto } from '../dtos/fido-user.dto'; + @Injectable() export class FidoService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly fidoServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('FidoService'); - } - async generateRegistrationOption(deviceFlag: boolean, email: string): Promise<{ response: object }> { - try { - const payload = { deviceFlag, email }; - return await this.natsClient.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); - } catch (error) { - throw new RpcException(error.response); + constructor( + @Inject('NATS_CLIENT') private readonly fidoServiceProxy: ClientProxy + ) { + super('FidoService'); + } + async generateRegistrationOption(deviceFlag: boolean, email:string): Promise<{response: object}> { + try { + const payload = { deviceFlag, email }; + return await this.sendNats(this.fidoServiceProxy, 'generate-registration-options', payload); + } catch (error) { + throw new RpcException(error.response); + } + } - } - async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, email: string): Promise<{ response: object }> { - const payload = { verifyRegistrationDetails: verifyRegistrationDto, email }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'verify-registration', payload); - } + async verifyRegistration(verifyRegistrationDto: VerifyRegistrationDto, email: string): Promise<{response: object}> { + const payload = { verifyRegistrationDetails: verifyRegistrationDto, email }; + return this.sendNats(this.fidoServiceProxy, 'verify-registration', payload); + } - async generateAuthenticationOption(generateAuthentication: GenerateAuthenticationDto): Promise<{ response: object }> { - const { userName } = generateAuthentication; - const payload = { userName }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'generate-authentication-options', payload); - } + async generateAuthenticationOption(generateAuthentication: GenerateAuthenticationDto) : Promise<{response: object}> { + const {userName} = generateAuthentication; + const payload = { userName }; + return this.sendNats(this.fidoServiceProxy, 'generate-authentication-options', payload); + } - async verifyAuthentication( - verifyAuthenticationDto: VerifyAuthenticationDto, - email: string - ): Promise<{ response: object }> { - const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, email }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'verify-authentication', payload); - } + async verifyAuthentication(verifyAuthenticationDto: VerifyAuthenticationDto, email: string): Promise<{response: object}> { + const payload = { verifyAuthenticationDetails: verifyAuthenticationDto, email }; + return this.sendNats(this.fidoServiceProxy, 'verify-authentication', payload); + } - async updateFidoUser( - updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, - credentialId: string - ): Promise<{ response: object }> { - const payload = { updateFidoUserDetailsDto, credentialId }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'update-user', payload); - } + async updateFidoUser(updateFidoUserDetailsDto: UpdateFidoUserDetailsDto, credentialId: string) : Promise<{response: object}> { + const payload = {updateFidoUserDetailsDto, credentialId}; + return this.sendNats(this.fidoServiceProxy, 'update-user', payload); + } - async fetchFidoUserDetails(email: string): Promise<{ response: string }> { - const payload = { email }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'fetch-fido-user-details', payload); - } + async fetchFidoUserDetails(email: string): Promise<{response: string}> { + const payload = { email }; + return this.sendNats(this.fidoServiceProxy, 'fetch-fido-user-details', payload); + } - async deleteFidoUserDevice(credentialId: string): Promise<{ response: object }> { - const payload = { credentialId }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'delete-fido-user-device', payload); - } + async deleteFidoUserDevice(credentialId: string): Promise<{response: object}> { + const payload = { credentialId }; + return this.sendNats(this.fidoServiceProxy, 'delete-fido-user-device', payload); + } - async updateFidoUserDeviceName(credentialId: string, deviceName: string): Promise<{ response: string }> { - const payload = { credentialId, deviceName }; - return this.natsClient.sendNats(this.fidoServiceProxy, 'update-fido-user-device-name', payload); - } + async updateFidoUserDeviceName(credentialId: string, deviceName: string): Promise<{response: string}> { + const payload = { credentialId, deviceName }; + return this.sendNats(this.fidoServiceProxy, 'update-fido-user-device-name', payload); + } } diff --git a/apps/api-gateway/src/geo-location/geo-location.controller.ts b/apps/api-gateway/src/geo-location/geo-location.controller.ts index 2bca8eecc..6143b38c0 100644 --- a/apps/api-gateway/src/geo-location/geo-location.controller.ts +++ b/apps/api-gateway/src/geo-location/geo-location.controller.ts @@ -17,8 +17,7 @@ export class GeoLocationController { ) {} /** - * Retrieve a list of all countries - * @returns A list of all available countries + * @returns get all countries */ @Get('countries') @ApiOperation({ summary: 'Retrieve a list of all countries', description: 'Fetches and returns the details of all available countries.' }) @@ -34,13 +33,13 @@ export class GeoLocationController { } /** - * Retrieve a list of all states within a specified country - * @param countryId The ID of the country - * @returns A list of all states associated with the given countryId + * @returns get all states by countryId */ + @Get('countries/:countryId/states') - @ApiOperation({ summary: 'Retrieve a list of all states within a specified country', description: 'Fetches and returns the details of all states associated with a given countryId.' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({summary: 'Retrieve a list of all states within a specified country', description: 'Fetches and returns the details of all states associated with a given countryId. ' + }) + @ApiResponse({status: HttpStatus.OK, description: 'Success', type: ApiResponseDto}) async getStatesByCountryId(@Param('countryId') countryId: number, @Res() res: Response): Promise { const statesDetails = await this.geolocationService.getStatesByCountryId(countryId); const finalResponse: IResponseType = { @@ -50,15 +49,11 @@ export class GeoLocationController { }; return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieve a list of all cities within a specified state and country - * @param countryId The ID of the country - * @param stateId The ID of the state - * @returns A list of all cities associated with the given countryId and stateId + * @returns get all cities by countryId and stateId */ @Get('countries/:countryId/states/:stateId/cities') - @ApiOperation({ summary: 'Retrieve a list of all cities within a specified state and country', description: 'Fetches and returns the details of all cities associated with a given countryId and stateId.' }) + @ApiOperation({summary: 'Retrieve a list of all cities within a specified state and country', description: 'Fetches and returns the details of all cities associated with a given countryId and stateId'}) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getCitiesByStateAndCountry(@Param('countryId') countryId: number, @Param('stateId') stateId: number, @Res() res: Response): Promise { const citiesDetails = await this.geolocationService.getCitiesByStateAndCountry(countryId, stateId); diff --git a/apps/api-gateway/src/geo-location/geo-location.module.ts b/apps/api-gateway/src/geo-location/geo-location.module.ts index 0f5db4d68..87da80148 100644 --- a/apps/api-gateway/src/geo-location/geo-location.module.ts +++ b/apps/api-gateway/src/geo-location/geo-location.module.ts @@ -6,7 +6,6 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { RateLimiterModule, RateLimiterGuard } from 'nestjs-rate-limiter'; import { APP_GUARD } from '@nestjs/core'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -31,8 +30,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; { provide: APP_GUARD, useClass: RateLimiterGuard - }, - NATSClient + } ] }) export class GeoLocationModule {} diff --git a/apps/api-gateway/src/geo-location/geo-location.service.ts b/apps/api-gateway/src/geo-location/geo-location.service.ts index 43aab40a5..3639a1b0c 100644 --- a/apps/api-gateway/src/geo-location/geo-location.service.ts +++ b/apps/api-gateway/src/geo-location/geo-location.service.ts @@ -1,15 +1,11 @@ import { CountryInterface, StateInterface, CityInterface } from '@credebl/common/interfaces/geolocation.interface'; import { Inject, Injectable } from '@nestjs/common'; -import { BaseService } from 'libs/service/base.service'; -import { NATSClient } from '@credebl/common/NATSClient'; import { ClientProxy } from '@nestjs/microservices'; +import { BaseService } from 'libs/service/base.service'; @Injectable() export class GeoLocationService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('GeoLocationService'); } @@ -20,7 +16,7 @@ export class GeoLocationService extends BaseService { */ async getAllCountries(): Promise { this.logger.log(`Finding all countries,GeoLocationService::getAllCountries`); - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-countries', ''); + return this.sendNatsMessage(this.serviceProxy, 'get-all-countries', ''); } /** @@ -31,7 +27,7 @@ export class GeoLocationService extends BaseService { async getStatesByCountryId(countryId: number): Promise { const payload = { countryId }; this.logger.log(`Finding cities for countryId= ${countryId},GeoLocationService::getCitiesByStateAndCountry`); - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-states', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-all-states', payload); } /** @@ -45,6 +41,6 @@ export class GeoLocationService extends BaseService { this.logger.log( `Finding cities for stateId= ${stateId} and countryId= ${countryId},GeoLocationService::getCitiesByStateAndCountry` ); - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-all-cities', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-all-cities', payload); } } diff --git a/apps/api-gateway/src/helper-files/file-operation.helper.ts b/apps/api-gateway/src/helper-files/file-operation.helper.ts index e02d7315b..19beaa73e 100644 --- a/apps/api-gateway/src/helper-files/file-operation.helper.ts +++ b/apps/api-gateway/src/helper-files/file-operation.helper.ts @@ -1,5 +1,5 @@ -import { promisify } from "util"; -import * as fs from "fs"; +import { promisify } from 'util'; +import * as fs from 'fs'; export const createFile = async ( diff --git a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts index 26b5e5ca4..f707b9a47 100644 --- a/apps/api-gateway/src/issuance/dtos/issuance.dto.ts +++ b/apps/api-gateway/src/issuance/dtos/issuance.dto.ts @@ -13,12 +13,10 @@ import { IsObject, IsOptional, IsString, - IsUUID, IsUrl, MaxLength, ValidateNested } from 'class-validator'; -import { AutoAccept, SchemaType, SortValue } from '@credebl/enum/enum'; import { IsCredentialJsonLdContext, SingleOrArray } from '../utils/helper'; import { IssueCredentialType, @@ -28,6 +26,7 @@ import { } from '../interfaces'; import { Transform, Type } from 'class-transformer'; +import { AutoAccept, SchemaType, SortValue } from '@credebl/enum/enum'; import { SortFields } from 'apps/connection/src/enum/connection.enum'; import { trim } from '@credebl/common/cast.helper'; @@ -56,18 +55,6 @@ class PrettyVc { @Transform(({ value }) => trim(value)) @IsString({ message: 'orientation must be in string format.' }) orientation: string; - - @ApiPropertyOptional({ example: '60px' }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsString({ message: 'height must be in string format.' }) - height?: string; - - @ApiPropertyOptional({ example: '60px' }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsString({ message: 'width must be in string format.' }) - width?: string; } export class Credential { @ApiProperty() @@ -250,8 +237,6 @@ export class CredentialsIssuanceDto { reuseConnection?: boolean; orgId: string; - - isValidateSchema?: boolean; } export class OOBIssueCredentialDto extends CredentialsIssuanceDto { @@ -487,8 +472,6 @@ export class OOBCredentialDtoWithEmail { imageUrl?: string; - isValidateSchema?: boolean; - orgId: string; } @@ -596,18 +579,6 @@ export class ClientDetails { @IsOptional() @IsString({ message: 'Orientation should be string' }) orientation?: string; - - @ApiPropertyOptional({ example: '60px' }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsString({ message: 'height must be in string format.' }) - height?: string; - - @ApiPropertyOptional({ example: '60px' }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsString({ message: 'width must be in string format.' }) - width?: string; } export class TemplateDetails { @@ -650,13 +621,13 @@ export class FileQuery { @ApiProperty({ required: true }) @IsString({ message: 'fileId should be string' }) @IsNotEmpty({ message: 'fileId Id is required' }) - @IsUUID('4', { message: 'Invalid format for file Id' }) @Transform(({ value }) => trim(value)) fileId: string; } export class RequestIdQuery { - @ApiProperty() + @ApiPropertyOptional({ required: false }) + @IsOptional() @IsString({ message: 'requestId should be string' }) @IsNotEmpty({ message: 'requestId Id is required' }) @Transform(({ value }) => trim(value)) diff --git a/apps/api-gateway/src/issuance/interfaces/index.ts b/apps/api-gateway/src/issuance/interfaces/index.ts index 06d30d3ff..981777f8f 100644 --- a/apps/api-gateway/src/issuance/interfaces/index.ts +++ b/apps/api-gateway/src/issuance/interfaces/index.ts @@ -70,8 +70,7 @@ export interface UploadedFileDetails { templateId: string; fileKey: string; fileName: string; - type: SchemaType; - isValidateSchema?: boolean; + type: SchemaType } export interface IIssuedCredentialSearchParams { pageNumber: number; diff --git a/apps/api-gateway/src/issuance/issuance.controller.ts b/apps/api-gateway/src/issuance/issuance.controller.ts index 86add6131..2a321bcda 100644 --- a/apps/api-gateway/src/issuance/issuance.controller.ts +++ b/apps/api-gateway/src/issuance/issuance.controller.ts @@ -33,8 +33,7 @@ import { ApiQuery, ApiExcludeEndpoint, ApiConsumes, - ApiBody, - ApiNotFoundResponse + ApiBody } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -63,12 +62,7 @@ import { Roles } from '../authz/decorators/roles.decorator'; import { OrgRoles } from 'libs/org-roles/enums'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; -import { - FileExportResponse, - IIssuedCredentialSearchParams, - IssueCredentialType, - UploadedFileDetails -} from './interfaces'; +import { FileExportResponse, IIssuedCredentialSearchParams, IssueCredentialType, UploadedFileDetails } from './interfaces'; import { AwsService } from '@credebl/aws'; import { FileInterceptor } from '@nestjs/platform-express'; import { v4 as uuidv4 } from 'uuid'; @@ -78,27 +72,21 @@ import { user } from '@prisma/client'; import { IGetAllIssuedCredentialsDto } from './dtos/get-all-issued-credentials.dto'; import { IssueCredentialDto } from './dtos/multi-connection.dto'; import { SchemaType } from '@credebl/enum/enum'; -import { CommonConstants } from '../../../../libs/common/src/common.constant'; -import { TrimStringParamPipe } from '@credebl/common/cast.helper'; -import { NotFoundErrorDto } from '../dtos/not-found-error.dto'; + @Controller() @UseFilters(CustomExceptionFilter) @ApiTags('credentials') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class IssuanceController { constructor( private readonly issueCredentialService: IssuanceService, private readonly awsService: AwsService - ) {} + ) { } private readonly logger = new Logger('IssuanceController'); /** - * Get all issued credentials for a specific organization - * @param orgId The ID of the organization - * @param getAllIssuedCredentials The query parameters for pagination and search - * @param user The user making the request - * @param res The response object + * @param orgId * @returns List of issued credentials for a specific organization */ @@ -107,7 +95,7 @@ export class IssuanceController { @ApiBearerAuth() @ApiOperation({ summary: `Get all issued credentials for a specific organization`, - description: `Retrieve all issued credentials for a specific organization. Supports pagination and search.` + description: `Get all issued credentials for a specific organization` }) @ApiQuery({ name: 'pageNumber', @@ -158,11 +146,8 @@ export class IssuanceController { } /** - * Fetch credentials by credentialRecordId - * @param credentialRecordId The ID of the credential record - * @param orgId The ID of the organization - * @param user The user making the request - * @param res The response object + * @param credentialRecordId + * @param orgId * @returns Details of specific credential */ @@ -170,23 +155,14 @@ export class IssuanceController { @ApiBearerAuth() @ApiOperation({ summary: `Fetch credentials by credentialRecordId`, - description: `Retrieve the details of a specific credential by its credentialRecordId.` + description: `Fetch credentials credentialRecordId` }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) async getIssueCredentialsbyCredentialRecordId( @User() user: IUserRequest, - @Param( - 'credentialRecordId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.issuance.error.invalidCredentialRecordId); - } - }) - ) - credentialRecordId: string, + @Param('credentialRecordId') credentialRecordId: string, @Param('orgId') orgId: string, @Res() res: Response ): Promise { @@ -204,20 +180,13 @@ export class IssuanceController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Fetch all templates for bulk operation - * @param orgId The ID of the organization - * @param schemaType The type of schema - * @param res The response object - * @returns List of templates for bulk operation - */ @Get('/orgs/:orgId/credentials/bulk/template') @ApiOperation({ - summary: 'Fetch all templates for bulk operation', - description: 'Retrieve all templates for a specific organization for bulk operation.' + summary: 'Fetch all templates for bulk opeartion', + description: 'Retrieve all templates for bulk operation' }) @ApiQuery({ - name: 'schemaType', + name:'schemaType', enum: SchemaType }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @@ -225,15 +194,7 @@ export class IssuanceController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getAllCredentialTemplates( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response, @Query('schemaType') schemaType: SchemaType = SchemaType.INDY ): Promise { @@ -246,80 +207,53 @@ export class IssuanceController { return res.status(HttpStatus.OK).json(credDefResponse); } - /** - * Download CSV template for bulk issuance - * @param orgId The ID of the organization - * @param templateDetails The details of the template - * @param res The response object - * @returns The CSV template for bulk issuance - */ - @Post('/orgs/:orgId/credentials/bulk/template') - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiNotFoundResponse({ description: 'Not Found', type: NotFoundErrorDto }) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Header('Content-Disposition', 'attachment; filename="schema.csv"') - @Header('Content-Type', 'application/csv') - @ApiOperation({ - summary: 'Download csv template for bulk-issuance', - description: 'Download csv template for a specific organization bulk-issuance using template details.' - }) - async downloadBulkIssuanceCSVTemplate( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @Body() templateDetails: TemplateDetails, - @Res() res: Response - ): Promise { - try { - const templateData: FileExportResponse = await this.issueCredentialService.downloadBulkIssuanceCSVTemplate( - orgId, - templateDetails - ); - return res - .header('Content-Disposition', `attachment; filename="${templateData.fileName}"`) - .status(HttpStatus.OK) - .send(templateData.fileContent); - } catch (error) { - return res - .status(error.statusCode || HttpStatus.INTERNAL_SERVER_ERROR) - .header('Content-Type', 'application/json') - .header('Content-Disposition', '') - .send(error); - } +@Post('/orgs/:orgId/credentials/bulk/template') +@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiBearerAuth() +@Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) +@UseGuards(AuthGuard('jwt'), OrgRolesGuard) +@Header('Content-Disposition', 'attachment; filename="schema.csv"') +@Header('Content-Type', 'application/csv') +@ApiOperation({ + summary: 'Download csv template for bulk-issuance', + description: 'Download csv template for bulk-issuance' +}) +async downloadBulkIssuanceCSVTemplate( + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Body() templateDetails: TemplateDetails, + @Res() res: Response +): Promise { + try { + const templateData: FileExportResponse = await this.issueCredentialService.downloadBulkIssuanceCSVTemplate( + orgId, templateDetails + ); + return res + .header('Content-Disposition', `attachment; filename="${templateData.fileName}"`) + .status(HttpStatus.OK) + .send(templateData.fileContent); + } catch (error) { + return res.status(error.status || HttpStatus.INTERNAL_SERVER_ERROR).json(error.error); } - /** - * Upload file for bulk issuance - * @param orgId The ID of the organization - * @param query The query parameters - * @param file The uploaded file - * @param fileDetails The details of the file - * @param res The response object - * @returns The details of the uploaded file - */ +} + @Post('/orgs/:orgId/bulk/upload') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @ApiOperation({ summary: 'Upload file for bulk issuance', - description: 'Upload a filled CSV file for bulk issuance.' + description: 'Upload file for bulk issuance.' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @@ -344,81 +278,61 @@ export class IssuanceController { required: true, description: 'The type of schema to be used' }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false - }) @UseInterceptors(FileInterceptor('file')) async uploadCSVTemplate( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Query(new ValidationPipe({ transform: true })) query: TemplateQuery, @UploadedFile() file: Express.Multer.File, @Body() fileDetails: object, @Res() res: Response, - @Query('schemaType') schemaType: SchemaType = SchemaType.INDY, - @Query('isValidateSchema') isValidateSchema: boolean = true + @Query('schemaType') schemaType: SchemaType = SchemaType.INDY ): Promise { const { templateId } = query; - if (file) { - const fileKey: string = uuidv4(); - try { - await this.awsService.uploadCsvFile(fileKey, file?.buffer); - } catch (error) { - throw new RpcException(error.response ? error.response : error); + if (file) { + const fileKey: string = uuidv4(); + try { + await this.awsService.uploadCsvFile(fileKey, file?.buffer); + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } + + const uploadedfileDetails: UploadedFileDetails = { + type: schemaType, + templateId, + fileKey, + fileName: fileDetails['fileName'] || file?.filename || file?.originalname + }; + + const importCsvDetails = await this.issueCredentialService.uploadCSVTemplate(uploadedfileDetails); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.issuance.success.importCSV, + data: importCsvDetails.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); } - const uploadedfileDetails: UploadedFileDetails = { - type: schemaType, - templateId, - fileKey, - fileName: fileDetails['fileName'] || file?.filename || file?.originalname, - isValidateSchema - }; - - const importCsvDetails = await this.issueCredentialService.uploadCSVTemplate(uploadedfileDetails, orgId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.issuance.success.importCSV, - data: importCsvDetails.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } } - /** - * Preview uploaded file details - * @param orgId The ID of the organization - * @param query The query parameters - * @param previewFileDetails The details of the file to preview - * @param res The response object - * @returns The preview of the uploaded file details - */ @Get('/orgs/:orgId/:requestId/preview') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER, OrgRoles.ISSUER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiOperation({ - summary: 'Preview uploaded file details', - description: 'Preview uploaded CSV file details for bulk issuance.' + summary: 'Preview uploded file details', + description: 'Preview uploded file details' }) @ApiQuery({ name: 'pageNumber', @@ -441,6 +355,7 @@ export class IssuanceController { @Query() previewFileDetails: PreviewFileDetails, @Res() res: Response ): Promise { + const { requestId } = query; const previewCSVDetails = await this.issueCredentialService.previewCSVDetails(requestId, orgId, previewFileDetails); const finalResponse: IResponse = { @@ -451,40 +366,25 @@ export class IssuanceController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Bulk issue credentials - * @param orgId The ID of the organization - * @param requestId The ID of the request - * @param clientDetails The details of the client - * @param query The query parameters - * @param file The uploaded file - * @param fileDetails The details of the file - * @param user The user making the request - * @param res The response object - * @returns The details of the bulk issued credentials - */ @Post('/orgs/:orgId/:requestId/bulk') - @Roles(OrgRoles.ADMIN, OrgRoles.OWNER, OrgRoles.ISSUER, OrgRoles.VERIFIER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiOperation({ summary: 'bulk issue credential', - description: 'Start bulk-issuance process for a specific requestId.' - }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false + description: 'bulk issue credential' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { @@ -500,28 +400,22 @@ export class IssuanceController { }, required: true }) - @UseInterceptors( - FileInterceptor('file', { - limits: { fieldSize: Number(process.env.FIELD_UPLOAD_SIZE) || CommonConstants.DEFAULT_FIELD_UPLOAD_SIZE } - }) - ) + @UseInterceptors(FileInterceptor('file')) + async issueBulkCredentials( @Body() clientDetails: ClientDetails, - @Param(new ValidationPipe({ transform: true })) params: RequestIdQuery, + @Param('requestId') requestId: string, @Param('orgId') orgId: string, @User() user: user, @Query(new ValidationPipe({ transform: true })) query: CredentialQuery, - @Query('isValidateSchema') isValidateSchema: boolean = true, @Res() res: Response, @Body() fileDetails?: object, @UploadedFile() file?: Express.Multer.File ): Promise { - const { requestId } = params; const { credDefId } = query; clientDetails.userId = user.id; let reqPayload; - - // Need to update logic for University DEMO + // Need to update logic for University DEMO if (file && clientDetails?.isSelectiveIssuance) { const fileKey: string = uuidv4(); try { @@ -536,45 +430,34 @@ export class IssuanceController { type: fileDetails?.['type'] }; } - const bulkIssuanceDetails = await this.issueCredentialService.issueBulkCredential( - requestId, - orgId, - clientDetails, - reqPayload, - isValidateSchema - ); + const bulkIssuanceDetails = await this.issueCredentialService.issueBulkCredential(requestId, orgId, clientDetails, reqPayload); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.issuance.success.bulkIssuance, - data: bulkIssuanceDetails - }; - return res.status(HttpStatus.CREATED).json(finalResponse); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.issuance.success.bulkIssuance, + data: bulkIssuanceDetails + }; + return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Get all file list uploaded for bulk operation - * @param orgId The ID of the organization - * @param fileParameter The query parameters for file details - * @param res The response object - * @returns The list of all files uploaded for bulk operation - */ @Get('/orgs/:orgId/bulk/files') - @Roles(OrgRoles.OWNER, OrgRoles.ISSUER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiOperation({ summary: 'Get all file list uploaded for bulk operation', - description: 'Retrieve the list of all files uploaded for bulk operation for a specific organization.' + description: 'Get all file list uploaded for bulk operation' }) async issuedFileDetails( @Param('orgId') orgId: string, @@ -590,31 +473,24 @@ export class IssuanceController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get uploaded file details by file ID - * @param orgId The ID of the organization - * @param query The query parameters for file details - * @param fileParameter The query parameters for file details - * @param res The response object - * @returns The details of the uploaded file by file ID - */ - @Get('/orgs/:orgId/:fileId/bulk/file-data') - @Roles(OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.OWNER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiOperation({ - summary: 'Get uploaded file details by file ID', - description: 'Retrieve the details of an uploaded file by its file ID for a specific organization.' + summary: 'Get uploaded file details by file id', + description: 'Get uploaded file details by file id' }) async getFileDetailsByFileId( @Param('orgId') orgId: string, @@ -632,87 +508,35 @@ export class IssuanceController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get all file details and file data by file ID - * @param orgId The ID of the organization - * @param query The query parameters for file details - * @param res The response object - * @returns The details and data of the uploaded file by file ID - */ - @Get('/orgs/:orgId/:fileId/bulk/file-details-and-file-data') - @Roles(OrgRoles.ADMIN, OrgRoles.VERIFIER, OrgRoles.ISSUER, OrgRoles.OWNER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - @ApiUnauthorizedResponse({ - description: 'Unauthorized', - type: UnauthorizedErrorDto - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiForbiddenResponse({ - description: 'Forbidden', - type: ForbiddenErrorDto - }) - @ApiOperation({ - summary: 'Get all file details and file data by file ID', - description: 'Retrieve all details and data of an uploaded file by its file ID for a specific organization.' - }) - async getFileDetailsAndFileDataByFileId( - @Param('orgId') orgId: string, - @Param(new ValidationPipe({ transform: true })) query: FileQuery, - @Res() res: Response - ): Promise { - const { fileId } = query; - const issuedFileDetails = await this.issueCredentialService.getFileDetailsAndFileDataByFileId(orgId, fileId); - const finalResponse: IResponseType = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.fileDetailsAndFileData, - data: issuedFileDetails - }; - return res.status(HttpStatus.OK).json(finalResponse); - } - /** - * Retry bulk issue credential - * @param fileId The ID of the file - * @param orgId The ID of the organization - * @param isValidateSchema Whether to validate the schema - * @param res The response object - * @param clientDetails The details of the client - * @returns The details of the retried bulk issued credentials - */ @Post('/orgs/:orgId/:fileId/retry/bulk') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiUnauthorizedResponse({ + status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) @ApiForbiddenResponse({ + status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) @ApiOperation({ summary: 'Retry bulk issue credential', - description: 'Retry the bulk issuance of credentials for a specific file ID and organization.' - }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false + description: 'Retry bulk issue credential' }) async retryBulkCredentials( @Param('fileId') fileId: string, @Param('orgId') orgId: string, - @Query('isValidateSchema') isValidateSchema: boolean = true, @Res() res: Response, @Body() clientDetails: ClientDetails ): Promise { const bulkIssuanceDetails = await this.issueCredentialService.retryBulkCredential( fileId, orgId, - clientDetails, - isValidateSchema + clientDetails ); const finalResponse: IResponseType = { statusCode: HttpStatus.CREATED, @@ -722,102 +546,75 @@ export class IssuanceController { return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * @param user + * @param orgId + * @param issueCredentialDto + * @param res + * @returns Issuer creates a credential offer and sends it to the holder + */ + @Post('/orgs/:orgId/credentials/offer') + @ApiBearerAuth() + @ApiOperation({ + summary: `Issuer create a credential offer`, + description: `Issuer creates a credential offer and sends it to the holder` + }) + @ApiQuery({ + name:'credentialType', + enum: IssueCredentialType + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async sendCredential( + @User() user: IUserRequest, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, + @Body() issueCredentialDto: IssueCredentialDto, + @Res() res: Response, + @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY + ): Promise { + issueCredentialDto.orgId = orgId; + issueCredentialDto.credentialType = credentialType; + + const credOffer = issueCredentialDto?.credentialData || []; + + if (IssueCredentialType.INDY !== credentialType && IssueCredentialType.JSONLD !== credentialType) { + throw new NotFoundException(ResponseMessages.issuance.error.invalidCredentialType); + } + + if (credentialType === IssueCredentialType.INDY && !issueCredentialDto.credentialDefinitionId) { + throw new BadRequestException(ResponseMessages.credentialDefinition.error.isRequired); + } + + if (issueCredentialDto.credentialType !== IssueCredentialType.INDY && !credOffer.every(offer => (!offer?.attributes || 0 === Object.keys(offer?.attributes).length))) { + throw new BadRequestException(ResponseMessages.issuance.error.attributesAreRequired); + } + + if (issueCredentialDto.credentialType === IssueCredentialType.JSONLD && credOffer.every(offer => (!offer?.credential || 0 === Object.keys(offer?.credential).length))) { + throw new BadRequestException(ResponseMessages.issuance.error.credentialNotPresent); + } + + if (issueCredentialDto.credentialType === IssueCredentialType.JSONLD && credOffer.every(offer => (!offer?.options || 0 === Object.keys(offer?.options).length))) { + throw new BadRequestException(ResponseMessages.issuance.error.optionsNotPresent); + } + const getCredentialDetails = await this.issueCredentialService.sendCredentialCreateOffer(issueCredentialDto, user); + const { statusCode, message, data} = getCredentialDetails; + + const finalResponse: IResponse = { + statusCode, + message, + data + }; + + return res.status(statusCode).json(finalResponse); + } /** - * Issuer create a credential offer - * @param user The user making the request - * @param orgId The ID of the organization - * @param issueCredentialDto The details of the credential to be issued - * @param res The response object - * @param credentialType The type of credential to be issued - * @returns The details of the created credential offer - */ - @Post('/orgs/:orgId/credentials/offer') - @ApiBearerAuth() - @ApiOperation({ - summary: `Issuer create a credential offer`, - description: `Issuer creates a credential offer and sends it to the holder` - }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false - }) - @ApiQuery({ - name: 'credentialType', - enum: IssueCredentialType - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - async sendCredential( - @User() user: IUserRequest, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); - } - }) - ) - orgId: string, - @Body() issueCredentialDto: IssueCredentialDto, - @Res() res: Response, - @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY, - @Query('isValidateSchema') isValidateSchema: boolean = true - ): Promise { - issueCredentialDto.orgId = orgId; - issueCredentialDto.credentialType = credentialType; - issueCredentialDto.isValidateSchema = isValidateSchema; - - const credOffer = issueCredentialDto?.credentialData || []; - - if (IssueCredentialType.INDY !== credentialType && IssueCredentialType.JSONLD !== credentialType) { - throw new NotFoundException(ResponseMessages.issuance.error.invalidCredentialType); - } - - if (credentialType === IssueCredentialType.INDY && !issueCredentialDto.credentialDefinitionId) { - throw new BadRequestException(ResponseMessages.credentialDefinition.error.isRequired); - } - - if ( - issueCredentialDto.credentialType !== IssueCredentialType.INDY && - !credOffer.every((offer) => !offer?.attributes || 0 === Object.keys(offer?.attributes).length) - ) { - throw new BadRequestException(ResponseMessages.issuance.error.attributesAreRequired); - } - - if ( - issueCredentialDto.credentialType === IssueCredentialType.JSONLD && - credOffer.every((offer) => !offer?.credential || 0 === Object.keys(offer?.credential).length) - ) { - throw new BadRequestException(ResponseMessages.issuance.error.credentialNotPresent); - } - - if ( - issueCredentialDto.credentialType === IssueCredentialType.JSONLD && - credOffer.every((offer) => !offer?.options || 0 === Object.keys(offer?.options).length) - ) { - throw new BadRequestException(ResponseMessages.issuance.error.optionsNotPresent); - } - const getCredentialDetails = await this.issueCredentialService.sendCredentialCreateOffer(issueCredentialDto, user); - const { statusCode, message, data } = getCredentialDetails; - - const finalResponse: IResponse = { - statusCode, - message, - data - }; - - return res.status(statusCode).json(finalResponse); - } - /** - * Creates a out-of-band credential offer and sends them via emails - * @param user The user making the request - * @param outOfBandCredentialDto The details of the out-of-band credential to be issued - * @param orgId The ID of the organization - * @param res The response object - * @param credentialType The type of credential to be issued - * @returns The details of the created out-of-band credential offer + * + * @param user + * @param outOfBandCredentialDto + * @param orgId + * @param res + * @returns Issuer creates a out-of-band credential offers and sends them to holders via emails */ @Post('/orgs/:orgId/credentials/oob/email') @UseGuards(AuthGuard('jwt')) @@ -830,41 +627,27 @@ export class IssuanceController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) @ApiQuery({ - name: 'credentialType', + name:'credentialType', enum: IssueCredentialType }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false - }) async outOfBandCredentialOffer( @User() user: IUserRequest, @Body() outOfBandCredentialDto: OOBCredentialDtoWithEmail, @Param('orgId') orgId: string, @Res() res: Response, - @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY, - @Query('isValidateSchema') isValidateSchema: boolean = true + @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY ): Promise { outOfBandCredentialDto.orgId = orgId; outOfBandCredentialDto.credentialType = credentialType; - outOfBandCredentialDto.isValidateSchema = isValidateSchema; - const credOffer = outOfBandCredentialDto?.credentialOffer || []; - if (IssueCredentialType.INDY !== credentialType && IssueCredentialType.JSONLD !== credentialType) { + if (IssueCredentialType.INDY !== credentialType && IssueCredentialType.JSONLD !== credentialType) { throw new NotFoundException(ResponseMessages.issuance.error.invalidCredentialType); - } - if ( - outOfBandCredentialDto.credentialType === IssueCredentialType.JSONLD && - credOffer.every((offer) => !offer?.credential || 0 === Object.keys(offer?.credential).length) - ) { +} + if (outOfBandCredentialDto.credentialType === IssueCredentialType.JSONLD && credOffer.every(offer => (!offer?.credential || 0 === Object.keys(offer?.credential).length))) { throw new BadRequestException(ResponseMessages.issuance.error.credentialNotPresent); } - if ( - outOfBandCredentialDto.credentialType === IssueCredentialType.JSONLD && - credOffer.every((offer) => !offer?.options || 0 === Object.keys(offer?.options).length) - ) { + if (outOfBandCredentialDto.credentialType === IssueCredentialType.JSONLD && credOffer.every(offer => (!offer?.options || 0 === Object.keys(offer?.options).length))) { throw new BadRequestException(ResponseMessages.issuance.error.optionsNotPresent); } const getCredentialDetails = await this.issueCredentialService.outOfBandCredentialOffer( @@ -880,59 +663,45 @@ export class IssuanceController { return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Create out-of-band credential offer - * @param user The user making the request - * @param issueCredentialDto The details of the out-of-band credential to be issued - * @param orgId The ID of the organization - * @param res The response object - * @param credentialType The type of credential to be issued - * @param isValidateSchema Whether to validate the schema - * @returns The details of the created out-of-band credential offer + /** + * Description: Issuer create out-of-band credential + * @param user + * @param issueCredentialDto */ - @Post('/orgs/:orgId/credentials/oob/offer') - @ApiBearerAuth() - @ApiOperation({ - summary: `Create out-of-band credential offer`, - description: `Creates an out-of-band credential offer` - }) - @ApiQuery({ - name: 'credentialType', - enum: IssueCredentialType - }) - @ApiQuery({ - name: 'isValidateSchema', - type: Boolean, - required: false - }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async createOOBCredentialOffer( - @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY, - @Query('isValidateSchema') isValidateSchema: boolean = true, - @Param('orgId') orgId: string, - @Body() issueCredentialDto: OOBIssueCredentialDto, - @Res() res: Response - ): Promise { - issueCredentialDto.orgId = orgId; - issueCredentialDto.credentialType = credentialType; - issueCredentialDto.isValidateSchema = isValidateSchema; - const getCredentialDetails = await this.issueCredentialService.sendCredentialOutOfBand(issueCredentialDto); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.issuance.success.create, - data: getCredentialDetails.response - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + @Post('/orgs/:orgId/credentials/oob/offer') + @ApiBearerAuth() + @ApiOperation({ + summary: `Create out-of-band credential offer`, + description: `Creates an out-of-band credential offer` + }) + @ApiQuery({ + name:'credentialType', + enum: IssueCredentialType + }) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async createOOBCredentialOffer( + @Query('credentialType') credentialType: IssueCredentialType = IssueCredentialType.INDY, + @Param('orgId') orgId: string, + @Body() issueCredentialDto: OOBIssueCredentialDto, + @Res() res: Response + ): Promise { + issueCredentialDto.orgId = orgId; + issueCredentialDto.credentialType = credentialType; + const getCredentialDetails = await this.issueCredentialService.sendCredentialOutOfBand(issueCredentialDto); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.issuance.success.create, + data: getCredentialDetails.response + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } /** - * Catch issue credential webhook responses - * @param issueCredentialDto The details of the issued credential - * @param id The ID of the organization - * @param res The response object - * @returns The details of the issued credential + * Description: webhook Save issued credential details + * @param user + * @param issueCredentialDto */ @Post('wh/:id/credentials') @ApiExcludeEndpoint() @@ -945,69 +714,57 @@ export class IssuanceController { @Param('id') id: string, @Res() res: Response ): Promise { - issueCredentialDto.type = 'Issuance'; - - if (id && 'default' === issueCredentialDto.contextCorrelationId) { - issueCredentialDto.orgId = id; - } +issueCredentialDto.type = 'Issuance'; - const getCredentialDetails = await this.issueCredentialService - .getIssueCredentialWebhook(issueCredentialDto, id) - .catch((error) => { +if (id && 'default' === issueCredentialDto.contextCorrelationId) { + issueCredentialDto.orgId = id; +} + + + const getAgentOrgDetail = await this.issueCredentialService.getIssueCredentialWebhook(issueCredentialDto, id).catch(error => { this.logger.debug(`error in saving issuance webhook ::: ${JSON.stringify(error)}`); }); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.issuance.success.create, - data: getCredentialDetails - }; - - const webhookUrl = await this.issueCredentialService - ._getWebhookUrl(issueCredentialDto.contextCorrelationId, id) - .catch((error) => { - this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`); - }); - if (webhookUrl) { - const plainIssuanceDto = JSON.parse(JSON.stringify(issueCredentialDto)); - - await this.issueCredentialService._postWebhookResponse(webhookUrl, { data: plainIssuanceDto }).catch((error) => { - this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); - }); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.issuance.success.create, + data: getAgentOrgDetail + }; + const webhookUrl: string | false = getAgentOrgDetail ? getAgentOrgDetail.response.webhookUrl : false; + if (webhookUrl) { + const plainIssuanceDto = JSON.parse(JSON.stringify(issueCredentialDto)); + + await this.issueCredentialService._postWebhookResponse(webhookUrl, {data: plainIssuanceDto}).catch(error => { + this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); + }); + } return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * Delete issuance record - * @param orgId The ID of the organization - * @param user The user making the request - * @param res The response object - * @returns The status of the deletion operation - */ - @Delete('/orgs/:orgId/issuance-records') - @ApiOperation({ summary: 'Delete issuance record', description: 'Delete issuance records by orgId' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async deleteIssuanceRecordsByOrgId( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @User() user: user, - @Res() res: Response - ): Promise { - await this.issueCredentialService.deleteIssuanceRecords(orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.issuance.success.deleteIssuanceRecords - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + } + + @Delete('/orgs/:orgId/issuance-records') + @ApiOperation({ summary: 'Delete issuance record', description: 'Delete issuance records by orgId' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async deleteIssuanceRecordsByOrgId( + @Param( + 'orgId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) + orgId: string, + @User() user: user, + @Res() res: Response + ): Promise { + await this.issueCredentialService.deleteIssuanceRecords(orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.issuance.success.deleteIssuanceRecords + }; + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/issuance/issuance.module.ts b/apps/api-gateway/src/issuance/issuance.module.ts index d5f184d16..ff92a8c87 100644 --- a/apps/api-gateway/src/issuance/issuance.module.ts +++ b/apps/api-gateway/src/issuance/issuance.module.ts @@ -5,9 +5,9 @@ import { IssuanceService } from './issuance.service'; import { CommonService } from '@credebl/common'; import { HttpModule } from '@nestjs/axios'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { ImageServiceService } from '@credebl/image-service'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -21,6 +21,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [IssuanceController], - providers: [IssuanceService, CommonService, AwsService, NATSClient] + providers: [IssuanceService, ImageServiceService, CommonService, AwsService] }) -export class IssuanceModule {} +export class IssuanceModule { } diff --git a/apps/api-gateway/src/issuance/issuance.service.ts b/apps/api-gateway/src/issuance/issuance.service.ts index b09b8a4a3..7fd9c6003 100644 --- a/apps/api-gateway/src/issuance/issuance.service.ts +++ b/apps/api-gateway/src/issuance/issuance.service.ts @@ -1,273 +1,164 @@ /* eslint-disable camelcase */ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { - ClientDetails, - FileParameter, - IssuanceDto, - OOBCredentialDtoWithEmail, - OOBIssueCredentialDto, - PreviewFileDetails, - TemplateDetails -} from './dtos/issuance.dto'; -import { - FileExportResponse, - IIssuedCredentialSearchParams, - IReqPayload, - ITemplateFormat, - IssueCredentialType, - UploadedFileDetails -} from './interfaces'; -import { - ICredentialOfferResponse, - IDeletedIssuanceRecords, - IIssuedCredential -} from '@credebl/common/interfaces/issuance.interface'; +import { ClientDetails, FileParameter, IssuanceDto, OOBCredentialDtoWithEmail, OOBIssueCredentialDto, PreviewFileDetails, TemplateDetails } from './dtos/issuance.dto'; +import { FileExportResponse, IIssuedCredentialSearchParams, IReqPayload, ITemplateFormat, IssueCredentialType, UploadedFileDetails } from './interfaces'; +import { ICredentialOfferResponse, IDeletedIssuanceRecords, IIssuedCredential } from '@credebl/common/interfaces/issuance.interface'; import { IssueCredentialDto } from './dtos/multi-connection.dto'; -import { user } from '@prisma/client'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; +import { org_agents, user } from '@prisma/client'; @Injectable() export class IssuanceService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly issuanceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('IssuanceService'); - } - sendCredentialCreateOffer( - issueCredentialDto: IssueCredentialDto, - user: IUserRequest - ): Promise { - const payload = { - comment: issueCredentialDto.comment, - credentialDefinitionId: issueCredentialDto.credentialDefinitionId, - credentialData: issueCredentialDto.credentialData, - orgId: issueCredentialDto.orgId, - protocolVersion: issueCredentialDto.protocolVersion, - autoAcceptCredential: issueCredentialDto.autoAcceptCredential, - credentialType: issueCredentialDto.credentialType, - isValidateSchema: issueCredentialDto.isValidateSchema, - user - }; - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'send-credential-create-offer', payload); - } + constructor( + @Inject('NATS_CLIENT') private readonly issuanceProxy: ClientProxy + ) { + super('IssuanceService'); + } - sendCredentialOutOfBand(issueCredentialDto: OOBIssueCredentialDto): Promise<{ - response: object; - }> { - const { - attributes, - comment, - options, - credentialDefinitionId, - orgId, - protocolVersion, - goalCode, - parentThreadId, - willConfirm, - label, - autoAcceptCredential, - credentialType, - isShortenUrl, - reuseConnection, - credential, - isValidateSchema - } = issueCredentialDto; + sendCredentialCreateOffer(issueCredentialDto: IssueCredentialDto, user: IUserRequest): Promise { - let payload; - if (IssueCredentialType.INDY === issueCredentialDto.credentialType) { - payload = { - attributes, - comment, - credentialDefinitionId, - orgId, - protocolVersion, - goalCode, - parentThreadId, - willConfirm, - label, - autoAcceptCredential, - credentialType, - isShortenUrl, - reuseConnection - }; - } - if (IssueCredentialType.JSONLD === issueCredentialDto.credentialType) { - payload = { - credential, - options, - comment, - orgId, - protocolVersion, - goalCode, - parentThreadId, - willConfirm, - label, - autoAcceptCredential, - credentialType, - isShortenUrl, - reuseConnection, - isValidateSchema - }; - } + const payload = { comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, credentialData: issueCredentialDto.credentialData, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType, user }; - return this.natsClient.sendNats(this.issuanceProxy, 'send-credential-create-offer-oob', payload); - } + return this.sendNatsMessage(this.issuanceProxy, 'send-credential-create-offer', payload); + } - getIssueCredentials( - issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams, - user: IUserRequest, - orgId: string - ): Promise { - const payload = { issuedCredentialsSearchCriteria, user, orgId }; - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'get-all-issued-credentials', payload); - } + sendCredentialOutOfBand(issueCredentialDto: OOBIssueCredentialDto): Promise<{ + response: object; + }> { + let payload; + if (IssueCredentialType.INDY === issueCredentialDto.credentialType) { + payload = { attributes: issueCredentialDto.attributes, comment: issueCredentialDto.comment, credentialDefinitionId: issueCredentialDto.credentialDefinitionId, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType, isShortenUrl: issueCredentialDto.isShortenUrl, reuseConnection : issueCredentialDto.reuseConnection }; + } + if (IssueCredentialType.JSONLD === issueCredentialDto.credentialType) { + payload = { credential: issueCredentialDto.credential, options: issueCredentialDto.options, comment: issueCredentialDto.comment, orgId: issueCredentialDto.orgId, protocolVersion: issueCredentialDto.protocolVersion, goalCode: issueCredentialDto.goalCode, parentThreadId: issueCredentialDto.parentThreadId, willConfirm: issueCredentialDto.willConfirm, label: issueCredentialDto.label, autoAcceptCredential: issueCredentialDto.autoAcceptCredential, credentialType: issueCredentialDto.credentialType, isShortenUrl: issueCredentialDto.isShortenUrl, reuseConnection : issueCredentialDto.reuseConnection }; + } + + return this.sendNats(this.issuanceProxy, 'send-credential-create-offer-oob', payload); + } - getIssueCredentialsbyCredentialRecordId( - user: IUserRequest, - credentialRecordId: string, - orgId: string - ): Promise<{ - response: object; - }> { - const payload = { user, credentialRecordId, orgId }; - return this.natsClient.sendNats(this.issuanceProxy, 'get-issued-credentials-by-credentialDefinitionId', payload); - } + getIssueCredentials(issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams, user: IUserRequest, orgId: string): Promise { + const payload = { issuedCredentialsSearchCriteria, user, orgId }; + return this.sendNatsMessage(this.issuanceProxy, 'get-all-issued-credentials', payload); + } - getIssueCredentialWebhook( - issueCredentialDto: IssuanceDto, - id: string - ): Promise<{ - response: object; - }> { - const payload = { issueCredentialDto, id }; - return this.natsClient.sendNats(this.issuanceProxy, 'webhook-get-issue-credential', payload); - } - outOfBandCredentialOffer( - user: IUserRequest, - outOfBandCredentialDto: OOBCredentialDtoWithEmail - ): Promise<{ - response: object; - }> { - const payload = { user, outOfBandCredentialDto }; - return this.natsClient.sendNats(this.issuanceProxy, 'out-of-band-credential-offer', payload); - } + getIssueCredentialsbyCredentialRecordId(user: IUserRequest, credentialRecordId: string, orgId: string): Promise<{ + response: object; + }> { + const payload = { user, credentialRecordId, orgId }; + return this.sendNats(this.issuanceProxy, 'get-issued-credentials-by-credentialDefinitionId', payload); + } - getAllCredentialTemplates(orgId: string, schemaType: string): Promise { - const payload = { orgId, schemaType }; - return this.natsClient.sendNatsMessage( - this.issuanceProxy, - 'get-all-credential-template-for-bulk-operation', - payload - ); - } + getIssueCredentialWebhook(issueCredentialDto: IssuanceDto, id: string): Promise<{response:org_agents}> { + const payload = { issueCredentialDto, id }; + return this.sendNats(this.issuanceProxy, 'webhook-get-issue-credential', payload); + } - async downloadBulkIssuanceCSVTemplate(orgId: string, templateDetails: TemplateDetails): Promise { - const payload = { orgId, templateDetails }; - return (await this.natsClient.sendNats(this.issuanceProxy, 'download-csv-template-for-bulk-operation', payload)) - .response; - } + outOfBandCredentialOffer(user: IUserRequest, outOfBandCredentialDto: OOBCredentialDtoWithEmail): Promise<{ + response: object; + }> { + const payload = { user, outOfBandCredentialDto }; + return this.sendNats(this.issuanceProxy, 'out-of-band-credential-offer', payload); + } - async uploadCSVTemplate(importFileDetails: UploadedFileDetails, orgId: string): Promise<{ response: object }> { - const payload = { importFileDetails, orgId }; - return this.natsClient.sendNats(this.issuanceProxy, 'upload-csv-template', payload); - } + getAllCredentialTemplates(orgId:string, schemaType:string): Promise { + const payload = { orgId, schemaType}; + return this.sendNatsMessage(this.issuanceProxy, 'get-all-credential-template-for-bulk-operation', payload); + } - async previewCSVDetails(requestId: string, orgId: string, previewFileDetails: PreviewFileDetails): Promise { - const payload = { - requestId, - orgId, - previewFileDetails - }; - return this.natsClient.sendNats(this.issuanceProxy, 'preview-csv-details', payload); - } + async downloadBulkIssuanceCSVTemplate(orgId: string, templateDetails: TemplateDetails + ): Promise { + const payload = { orgId, templateDetails }; + return (await this.sendNats(this.issuanceProxy, 'download-csv-template-for-bulk-operation', payload)).response; + } - async issuedFileDetails(orgId: string, fileParameter: FileParameter): Promise<{ response: object }> { - const payload = { - orgId, - fileParameter - }; - return this.natsClient.sendNats(this.issuanceProxy, 'issued-file-details', payload); - } + async uploadCSVTemplate(importFileDetails: UploadedFileDetails + ): Promise<{ response: object }> { + const payload = { importFileDetails }; + return this.sendNats(this.issuanceProxy, 'upload-csv-template', payload); + } - async getFileDetailsByFileId( - orgId: string, - fileId: string, - fileParameter: FileParameter - ): Promise<{ response: object }> { - const payload = { - orgId, - fileId, - fileParameter - }; - return this.natsClient.sendNats(this.issuanceProxy, 'issued-file-data', payload); - } + async previewCSVDetails(requestId: string, + orgId: string, + previewFileDetails: PreviewFileDetails + ): Promise { + const payload = { + requestId, + orgId, + previewFileDetails + }; + return this.sendNats(this.issuanceProxy, 'preview-csv-details', payload); + } - async issueBulkCredential( - requestId: string, - orgId: string, - clientDetails: ClientDetails, - reqPayload: IReqPayload, - isValidateSchema: boolean - ): Promise { - const payload = { requestId, orgId, clientDetails, reqPayload, isValidateSchema }; + async issuedFileDetails( + orgId: string, + fileParameter: FileParameter + ): Promise<{ response: object }> { + const payload = { + orgId, + fileParameter + }; + return this.sendNats(this.issuanceProxy, 'issued-file-details', payload); + } - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'issue-bulk-credentials', payload); - } + async getFileDetailsByFileId( + orgId: string, + fileId: string, + fileParameter: FileParameter + ): Promise<{ response: object }> { + const payload = { + orgId, + fileId, + fileParameter + }; + return this.sendNats(this.issuanceProxy, 'issued-file-data', payload); + } - async retryBulkCredential( - fileId: string, - orgId: string, - clientDetails: ClientDetails, - isValidateSchema?: boolean - ): Promise { - const payload = { fileId, orgId, clientDetails, isValidateSchema }; - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'retry-bulk-credentials', payload); - } + async issueBulkCredential(requestId: string, orgId: string, clientDetails: ClientDetails, reqPayload: IReqPayload): Promise { + const payload = { requestId, orgId, clientDetails, reqPayload }; + return this.sendNatsMessage(this.issuanceProxy, 'issue-bulk-credentials', payload); + } - async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { - const pattern = { cmd: 'get-webhookurl' }; - const payload = { tenantId, orgId }; + async retryBulkCredential(fileId: string, orgId: string, clientDetails: ClientDetails): Promise { + const payload = { fileId, orgId, clientDetails }; + return this.sendNatsMessage(this.issuanceProxy, 'retry-bulk-credentials', payload); + } - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.issuanceProxy.send(pattern, payload).toPromise(); - return message; - } catch (error) { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw error; + async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { + const pattern = { cmd: 'get-webhookurl' }; + const payload = { tenantId, orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.issuanceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw error; + } } - } - async _postWebhookResponse(webhookUrl: string, data: object): Promise { - const pattern = { cmd: 'post-webhook-response-to-webhook-url' }; - const payload = { webhookUrl, data }; + async _postWebhookResponse(webhookUrl: string, data: object): Promise { + const pattern = { cmd: 'post-webhook-response-to-webhook-url' }; + const payload = { webhookUrl, data }; - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.issuanceProxy.send(pattern, payload).toPromise(); - return message; - } catch (error) { - this.logger.error(`catch: ${JSON.stringify(error)}`); + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.issuanceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + + throw error; + } + } - throw error; + async deleteIssuanceRecords(orgId: string, userDetails: user): Promise { + const payload = { orgId, userDetails }; + return this.sendNatsMessage(this.issuanceProxy, 'delete-issuance-records', payload); } - } - async deleteIssuanceRecords(orgId: string, userDetails: user): Promise { - const payload = { orgId, userDetails }; - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'delete-issuance-records', payload); - } - async getFileDetailsAndFileDataByFileId(orgId: string, fileId: string): Promise { - const payload = { - orgId, - fileId - }; - return this.natsClient.sendNatsMessage(this.issuanceProxy, 'issued-file-data-and-file-details', payload); - } } diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index e92468ce8..f0f59cd4d 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -1,42 +1,28 @@ -import { otelSDK } from './tracer'; import * as dotenv from 'dotenv'; import * as express from 'express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; -import * as cookieParser from 'cookie-parser'; +import { Logger } from '@nestjs/common'; + import { AppModule } from './app.module'; import { HttpAdapterHost, NestFactory, Reflector } from '@nestjs/core'; import { AllExceptionsFilter } from '@credebl/common/exception-handler'; -import { type MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; - import helmet from 'helmet'; +import { NodeEnvironment } from '@credebl/enum/enum'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; -import { NatsInterceptor } from '@credebl/common'; import { UpdatableValidationPipe } from '@credebl/common/custom-overrideable-validation-pipe'; -import * as useragent from 'express-useragent'; dotenv.config(); async function bootstrap(): Promise { - try { - if (otelSDK) { - await otelSDK.start(); - // eslint-disable-next-line no-console - console.log('OpenTelemetry SDK started successfully'); - } else { - // eslint-disable-next-line no-console - console.log('OpenTelemetry SDK disabled for this environment'); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Failed to start OpenTelemetry SDK:', error); - } - const app = await NestFactory.create(AppModule); - - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + const app = await NestFactory.create(AppModule, { + logger: + NodeEnvironment.PRODUCTION !== process.env.PLATFORM_PROFILE_MODE + ? ['log', 'debug', 'error', 'verbose', 'warn'] + : ['error', 'warn'] + }); app.connectMicroservice({ transport: Transport.NATS, @@ -45,12 +31,10 @@ async function bootstrap(): Promise { const expressApp = app.getHttpAdapter().getInstance(); expressApp.set('x-powered-by', false); - app.use(express.json({ limit: '100mb' })); - app.use(express.urlencoded({ limit: '100mb', extended: true })); - app.use(cookieParser()); - app.use(useragent.express()); + app.use(express.json({ limit: '50mb' })); + app.use(express.urlencoded({ limit: '50mb', extended: true })); - app.use((req, res, next) => { + app.use(function (req, res, next) { let err = null; try { decodeURIComponent(req.path); @@ -62,7 +46,6 @@ async function bootstrap(): Promise { } next(); }); - const options = new DocumentBuilder() .setTitle(`${process.env.PLATFORM_NAME}`) .setDescription(`${process.env.PLATFORM_NAME} Platform APIs`) @@ -77,14 +60,9 @@ async function bootstrap(): Promise { .addServer(`${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_GATEWAY_HOST}`) .build(); - app.enableVersioning({ - type: VersioningType.URI, - defaultVersion: ['1'] - }); - const document = SwaggerModule.createDocument(app, options); SwaggerModule.setup('api', app, document); - const httpAdapter: HttpAdapterHost = app.get(HttpAdapterHost) as HttpAdapterHost; + const httpAdapter = app.get(HttpAdapterHost); app.useGlobalFilters(new AllExceptionsFilter(httpAdapter)); const { ENABLE_CORS_IP_LIST } = process.env || {}; if (ENABLE_CORS_IP_LIST && '' !== ENABLE_CORS_IP_LIST) { @@ -95,11 +73,6 @@ async function bootstrap(): Promise { }); } - app.enableVersioning({ - type: VersioningType.URI, - defaultVersion: ['1', VERSION_NEUTRAL] - }); - app.use(express.static('uploadedFiles/holder-profile')); app.use(express.static('uploadedFiles/org-logo')); app.use(express.static('uploadedFiles/tenant-logo')); @@ -109,7 +82,6 @@ async function bootstrap(): Promise { app.use(express.static('invoice-pdf')); app.use(express.static('uploadedFiles/bulk-verification-templates')); app.use(express.static('uploadedFiles/import')); - // Use custom updatable global pipes const reflector = app.get(Reflector); app.useGlobalPipes(new UpdatableValidationPipe(reflector, { whitelist: true, transform: true })); app.use( @@ -117,7 +89,6 @@ async function bootstrap(): Promise { xssFilter: true }) ); - app.useGlobalInterceptors(new NatsInterceptor()); await app.listen(process.env.API_GATEWAY_PORT, `${process.env.API_GATEWAY_HOST}`); Logger.log(`API Gateway is listening on port ${process.env.API_GATEWAY_PORT}`); } diff --git a/apps/api-gateway/src/notification/notification.controller.ts b/apps/api-gateway/src/notification/notification.controller.ts index 1a8489bf2..22c82fc24 100644 --- a/apps/api-gateway/src/notification/notification.controller.ts +++ b/apps/api-gateway/src/notification/notification.controller.ts @@ -1,13 +1,6 @@ import { CustomExceptionFilter } from '@credebl/common/exception-handler'; import { Body, Controller, HttpStatus, Logger, Post, Res, UseFilters } from '@nestjs/common'; -import { - ApiExcludeEndpoint, - ApiForbiddenResponse, - ApiOperation, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -17,67 +10,73 @@ import { Response } from 'express'; import { ResponseMessages } from '@credebl/common/response-messages'; import { NotificationService } from './notification.service'; + @Controller('notification') @UseFilters(CustomExceptionFilter) @ApiTags('notification') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class NotificationController { - constructor(private readonly notificationService: NotificationService) {} - private readonly logger = new Logger('NotificationController'); + constructor( + private readonly notificationService: NotificationService + ) { } + private readonly logger = new Logger('NotificationController'); + + /** + * Register organization webhook endpoint + * @param registerOrgWebhhookEndpointDto + * @param res + * @returns Stored notification data + */ + @Post('/register/webhook-endpoint') + @ApiOperation({ + summary: `Register organization webhook endpoint for notification`, + description: `Register organization webhook endpoint for notification` + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async registerOrgWebhookEndpoint( + @Body() registerOrgWebhhookEndpointDto: RegisterOrgWebhhookEndpointDto, + @Res() res: Response + ): Promise { + + const registerUserEndpoint = await this.notificationService.registerOrgWebhookEndpoint( + registerOrgWebhhookEndpointDto + ); - /** - * Register organization webhook endpoint - * @param registerOrgWebhhookEndpointDto - * @param res - * @returns Stored notification data - */ - @Post('/register/webhook-endpoint') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: `Register organization webhook endpoint for notification`, - description: `Register organization webhook endpoint for notification` - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async registerOrgWebhookEndpoint( - @Body() registerOrgWebhhookEndpointDto: RegisterOrgWebhhookEndpointDto, - @Res() res: Response - ): Promise { - const registerUserEndpoint = - await this.notificationService.registerOrgWebhookEndpoint(registerOrgWebhhookEndpointDto); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.notification.success.register, + data: registerUserEndpoint + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.notification.success.register, - data: registerUserEndpoint - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Send notification for holder + * @param sendNotificationDto + * @param res + * @returns Get notification details + */ + @Post('/') + @ApiOperation({ + summary: `Send notification for holder`, + description: `Send notification for holder` + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + async sendNotification( + @Body() notificationRequestBody: SendNotificationDto, + @Res() res: Response + ): Promise { - /** - * Send notification for holder - * @param sendNotificationDto - * @param res - * @returns Get notification details - */ - @Post('/') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: `Send notification for holder`, - description: `Send notification for holder` - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async sendNotification( - @Body() notificationRequestBody: SendNotificationDto, - @Res() res: Response - ): Promise { - const sendNotification = await this.notificationService.sendNotification(notificationRequestBody); + const sendNotification = await this.notificationService.sendNotification( + notificationRequestBody + ); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.notification.success.sendNotification, - data: sendNotification - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } -} + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.notification.success.sendNotification, + data: sendNotification + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/notification/notification.module.ts b/apps/api-gateway/src/notification/notification.module.ts index 72fbc9400..d368a572b 100644 --- a/apps/api-gateway/src/notification/notification.module.ts +++ b/apps/api-gateway/src/notification/notification.module.ts @@ -8,7 +8,6 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { NotificationController } from './notification.controller'; import { NotificationService } from './notification.service'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -25,6 +24,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [NotificationController], - providers: [NotificationService, CommonService, NATSClient] + providers: [NotificationService, CommonService] }) export class NotificationModule { } diff --git a/apps/api-gateway/src/notification/notification.service.ts b/apps/api-gateway/src/notification/notification.service.ts index d1f3e3e03..01847e1d9 100644 --- a/apps/api-gateway/src/notification/notification.service.ts +++ b/apps/api-gateway/src/notification/notification.service.ts @@ -1,40 +1,30 @@ import { Inject, Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { RegisterOrgWebhhookEndpointDto, SendNotificationDto } from './dtos/notification.dto'; import { INotification } from './interfaces/notification.interfaces'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class NotificationService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('NotificationService'); - } + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { + super('NotificationService'); + } - /** - * Register organization webhook endpoint - * @param registerOrgWebhhookEndpointDto - * @returns Stored notification data - */ - async registerOrgWebhookEndpoint( - registerOrgWebhhookEndpointDto: RegisterOrgWebhhookEndpointDto - ): Promise { - return this.natsClient.sendNatsMessage( - this.serviceProxy, - 'register-org-webhook-endpoint-for-notification', - registerOrgWebhhookEndpointDto - ); - } + /** + * Register organization webhook endpoint + * @param registerOrgWebhhookEndpointDto + * @returns Stored notification data + */ + async registerOrgWebhookEndpoint(registerOrgWebhhookEndpointDto: RegisterOrgWebhhookEndpointDto): Promise { + return this.sendNatsMessage(this.serviceProxy, 'register-org-webhook-endpoint-for-notification', registerOrgWebhhookEndpointDto); + } - /** - * Send notification for holder - * @param sendNotificationDto - * @returns Get notification details - */ - async sendNotification(notificationRequestBody: SendNotificationDto): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'send-notification', notificationRequestBody); - } -} + /** + * Send notification for holder + * @param sendNotificationDto + * @returns Get notification details + */ + async sendNotification(notificationRequestBody: SendNotificationDto): Promise { + return this.sendNatsMessage(this.serviceProxy, 'send-notification', notificationRequestBody); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/dtos/client-token.dto.ts b/apps/api-gateway/src/organization/dtos/client-token.dto.ts deleted file mode 100644 index abd95d8e7..000000000 --- a/apps/api-gateway/src/organization/dtos/client-token.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; - -export class ClientTokenDto { - @ApiProperty() - @IsString({ message: 'orgId must be in string format.' }) - orgId: string; - - @ApiProperty() - @IsString({ message: 'clientAlias must be in string format.' }) - clientAlias: string; - - @ApiProperty() - @IsString({ message: 'clientId must be in string format.' }) - clientId: string; - - @ApiProperty() - @IsString({ message: 'clientSecret must be in string format.' }) - clientSecret: string; - - @ApiProperty() - @IsString({ message: 'grantType must be in string format.' }) - grantType?: string = 'client_credentials'; -} diff --git a/apps/api-gateway/src/organization/dtos/create-organization-dto.ts b/apps/api-gateway/src/organization/dtos/create-organization-dto.ts index 47d1f987b..820394d91 100644 --- a/apps/api-gateway/src/organization/dtos/create-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/create-organization-dto.ts @@ -1,26 +1,25 @@ import { ApiExtraModels, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsNotEmpty, IsOptional, IsString, IsUrl, MaxLength, MinLength } from 'class-validator'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUrl, MaxLength, MinLength } from 'class-validator'; import { Transform } from 'class-transformer'; import { IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; -import { GeoLocationDto } from '../../dtos/geo-location-dto'; @ApiExtraModels() -export class CreateOrganizationDto extends GeoLocationDto { +export class CreateOrganizationDto { @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'Organization name is required.' }) @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) - @MaxLength(200, { message: 'Organization name must be at most 200 characters.' }) + @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) @IsString({ message: 'Organization name must be in string format.' }) - @IsNotSQLInjection({ message: 'Incorrect pattern for organization name.' }) + @IsNotSQLInjection({ message: 'Organization name is required.' }) name: string; @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'Description is required.' }) @MinLength(2, { message: 'Description must be at least 2 characters.' }) - @MaxLength(1000, { message: 'Description must be at most 1000 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) @IsString({ message: 'Description must be in string format.' }) description: string; @@ -53,4 +52,22 @@ export class CreateOrganizationDto extends GeoLocationDto { @Transform(({ value }) => trim(value)) @IsString({ message: 'registrationNumber must be in string format.' }) registrationNumber?: string; + + @ApiPropertyOptional({ example: 101 }) + @IsOptional() + @IsNotEmpty({ message: 'country is required' }) + @IsNumber({}, { message: 'countryId must be a number' }) + countryId?: number; + + @ApiPropertyOptional({ example: 4008 }) + @IsOptional() + @IsNotEmpty({ message: 'state is required' }) + @IsNumber({}, { message: 'stateId must be a number' }) + stateId?: number; + + @ApiPropertyOptional({ example: 1000 }) + @IsOptional() + @IsNotEmpty({ message: 'city is required' }) + @IsNumber({}, { message: 'cityId must be a number' }) + cityId?: number; } diff --git a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts index b5c6295bd..456215420 100644 --- a/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts +++ b/apps/api-gateway/src/organization/dtos/send-invitation.dto.ts @@ -6,7 +6,7 @@ import { trim } from '@credebl/common/cast.helper'; @ApiExtraModels() export class SendInvitationDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) @@ -27,7 +27,7 @@ export class BulkSendInvitationDto { @ApiProperty({ example: [ { - email: 'awqx@yopmail.com', + email: 'awqx@getnada.com', orgRoleId: ['1a7eac11-ff05-40d7-8351-4d7467687cad'] } ] diff --git a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts index ca6e2dc8b..ed2029c4f 100644 --- a/apps/api-gateway/src/organization/dtos/update-organization-dto.ts +++ b/apps/api-gateway/src/organization/dtos/update-organization-dto.ts @@ -2,45 +2,46 @@ import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, IsBoolean, MaxLength, MinLength, Validate } from 'class-validator'; import { Transform } from 'class-transformer'; -import { ImageBase64Validator, IsNotSQLInjection, trim } from '@credebl/common/cast.helper'; -import { GeoLocationDto } from '../../dtos/geo-location-dto'; +import { ImageBase64Validator, trim } from '@credebl/common/cast.helper'; @ApiExtraModels() -export class UpdateOrganizationDto extends GeoLocationDto { - orgId: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Organization name is required.' }) - @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) - @MaxLength(200, { message: 'Organization name must be at most 200 characters.' }) - @IsString({ message: 'Organization name must be in string format.' }) - @IsNotSQLInjection({ message: 'Incorrect pattern for organization name.' }) - name: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => trim(value)) - @IsNotEmpty({ message: 'Description is required.' }) - @MinLength(2, { message: 'Description must be at least 2 characters.' }) - @MaxLength(1000, { message: 'Description must be at most 1000 characters.' }) - @IsString({ message: 'Description must be in string format.' }) - description: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => trim(value)) - @Validate(ImageBase64Validator) - logo?: string = ''; - - @ApiPropertyOptional() - @IsOptional() - website?: string; - - @ApiPropertyOptional({ example: true }) - @IsOptional() - @IsBoolean({ message: 'isPublic should be boolean' }) - @IsOptional() - isPublic?: boolean = false; -} +export class UpdateOrganizationDto { + + + orgId: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Organization name is required.' }) + @MinLength(2, { message: 'Organization name must be at least 2 characters.' }) + @MaxLength(50, { message: 'Organization name must be at most 50 characters.' }) + @IsString({ message: 'Organization name must be in string format.' }) + name: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Description is required.' }) + @MinLength(2, { message: 'Description must be at least 2 characters.' }) + @MaxLength(255, { message: 'Description must be at most 255 characters.' }) + @IsString({ message: 'Description must be in string format.' }) + description: string; + + @ApiPropertyOptional() + @IsOptional() + @Transform(({ value }) => trim(value)) + @Validate(ImageBase64Validator) + logo?: string = ''; + + @ApiPropertyOptional() + @IsOptional() + website?: string; + + @ApiPropertyOptional({ example: true }) + @IsOptional() + @IsBoolean({ message: 'isPublic should be boolean' }) + @IsOptional() + isPublic?: boolean = false; + +} \ No newline at end of file diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 6a99bf5cc..7b18068ba 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -1,36 +1,9 @@ -import { - ApiBearerAuth, - ApiExcludeEndpoint, - ApiForbiddenResponse, - ApiOperation, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; +import { ApiBearerAuth, ApiExcludeEndpoint, ApiForbiddenResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { CommonService } from '@credebl/common'; -import { - Controller, - Get, - Put, - Param, - UseGuards, - UseFilters, - Post, - Body, - Res, - HttpStatus, - Query, - Delete, - ParseUUIDPipe, - BadRequestException, - ValidationPipe, - UsePipes -} from '@nestjs/common'; +import { Controller, Get, Put, Param, UseGuards, UseFilters, Post, Body, Res, HttpStatus, Query, Delete, ParseUUIDPipe, BadRequestException, ValidationPipe, UsePipes } from '@nestjs/common'; import { OrganizationService } from './organization.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; -import IResponse from '@credebl/common/interfaces/response.interface'; +import IResponse from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -47,6 +20,7 @@ import { UpdateUserRolesDto } from './dtos/update-user-roles.dto'; import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; +import { ImageServiceService } from '@credebl/image-service'; import { ClientCredentialsDto } from './dtos/client-credentials.dto'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; import { validate as isValidUUID } from 'uuid'; @@ -54,58 +28,46 @@ import { UserAccessGuard } from '../authz/guards/user-access-guard'; import { GetAllOrganizationsDto } from './dtos/get-organizations.dto'; import { PrimaryDid } from './dtos/set-primary-did.dto'; import { TrimStringParamPipe } from '@credebl/common/cast.helper'; -import { ClientTokenDto } from './dtos/client-token.dto'; @UseFilters(CustomExceptionFilter) @Controller('orgs') @ApiTags('organizations') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class OrganizationController { + constructor( private readonly organizationService: OrganizationService, + private readonly imageServiceService: ImageServiceService, private readonly commonService: CommonService - ) {} + ) { } + +/** + * @param orgId + * @returns Organization logo image + */ - /** - * Get organization profile details - * @param orgId The ID of the organization - * @returns Organization logo image - */ @Get('/profile/:orgId') @ApiOperation({ summary: 'Organization Profile', description: 'Get organization profile details' }) @ApiExcludeEndpoint() @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async getOrgPofile( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @Res() res: Response - ): Promise { + async getOrgPofile(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) + orgId: string, @Res() res: Response): Promise { + const orgProfile = await this.organizationService.getOrgPofile(orgId); const base64Data = orgProfile['logoUrl']; - const getImageBuffer = await this.organizationService.getBase64Image(base64Data); - res.setHeader('Content-Type', 'image/png'); + const getImageBuffer = await this.imageServiceService.getBase64Image(base64Data); + res.setHeader('Content-Type', 'image/png'); return res.send(getImageBuffer); } - /** - * Get all public profile organizations - * @returns List of public organizations - */ +/** + * @returns List of public organizations + */ @Get('/public-profile') @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Get all public profile organizations', - description: 'Retrieve a list of all public profile organizations. Supports pagination and search.' - }) + @ApiOperation({ summary: 'Get all public profile organizations', description: 'Get all public profile organizations.' }) @ApiQuery({ name: 'pageNumber', type: Number, @@ -122,6 +84,7 @@ export class OrganizationController { required: false }) async get(@Query() paginationDto: PaginationDto, @Res() res: Response): Promise { + const users = await this.organizationService.getPublicOrganizations(paginationDto); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -132,21 +95,20 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Fetch org-roles details - * @param orgId The ID of the organization - * @returns Organization roles details - */ +/** + * @returns get organization roles + */ + @Get('/:orgId/roles') @ApiOperation({ summary: 'Fetch org-roles details', - description: 'Retrieve the roles details for a specific organization.' + description: 'Fetch org-roles details' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) + @UseGuards(AuthGuard('jwt')) @ApiBearerAuth() - async getOrgRoles(@Param('orgId') orgId: string, @User() user: user, @Res() res: Response): Promise { + async getOrgRoles(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: user, @Res() res: Response): Promise { + const orgRoles = await this.organizationService.getOrgRoles(orgId.trim(), user); const finalResponse: IResponse = { @@ -156,17 +118,18 @@ export class OrganizationController { }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Fetch organization details - * @param orgSlug The slug of the organization - * @returns Organization details - */ +/** + * @param orgSlug + * @returns organization details + */ @Get('public-profiles/:orgSlug') @ApiOperation({ summary: 'Fetch organization details', - description: 'Retrieve the details of a specific organization using its slug.' + description: 'Fetch organization details' }) + @ApiParam({ name: 'orgSlug', type: String, @@ -175,7 +138,7 @@ export class OrganizationController { async getPublicProfile(@Param('orgSlug') orgSlug: string, @Res() res: Response): Promise { // eslint-disable-next-line no-param-reassign orgSlug = orgSlug.trim(); - + if (!orgSlug.length) { throw new BadRequestException(ResponseMessages.organisation.error.orgSlugIsRequired); } @@ -188,24 +151,22 @@ export class OrganizationController { }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get organization dashboard details - * @param orgId The ID of the organization - * @returns Organization dashboard details - */ +/** + * @param orgId + * @returns Organization dashboard details + */ + @Get('/dashboard/:orgId') @ApiOperation({ summary: 'Get dashboard details', description: 'Get organization dashboard details' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - async getOrganizationDashboard( - @Param('orgId') orgId: string, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async getOrganizationDashboard(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + const getOrganization = await this.organizationService.getOrganizationDashboard(orgId, reqUser.id); const finalResponse: IResponse = { @@ -214,34 +175,17 @@ export class OrganizationController { data: getOrganization }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get organization references count - * @param orgId The ID of the organization - * @returns Organization references count - */ + @Get('/activity-count/:orgId') - @ApiOperation({ - summary: 'Get organization references count', - description: 'Retrieve the count of references for a specific organization.' - }) + @ApiOperation({ summary: 'Get organization references count', description: 'Get organization references count by org Id' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @Roles(OrgRoles.OWNER) - async getOrganizationActivityCount( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async getOrganizationActivityCount(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + const getOrganization = await this.organizationService.getOrganizationActivityCount(orgId, reqUser.id); const finalResponse: IResponse = { @@ -250,17 +194,11 @@ export class OrganizationController { data: getOrganization }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get all invitations - * @param orgId The ID of the organization - * @returns List of all invitations - */ + @Get('/:orgId/invitations') - @ApiOperation({ - summary: 'Get all invitations', - description: 'Retrieve a list of all invitations for a specific organization. Supports pagination and search.' - }) + @ApiOperation({ summary: 'Get all invitations', description: 'Get all invitations' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @@ -280,11 +218,8 @@ export class OrganizationController { required: false }) @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - async getInvitationsByOrgId( - @Param('orgId') orgId: string, - @Query() paginationDto: PaginationDto, - @Res() res: Response - ): Promise { + async getInvitationsByOrgId(@Param('orgId') orgId: string, @Query() paginationDto: PaginationDto, @Res() res: Response): Promise { + const getInvitationById = await this.organizationService.getInvitationsByOrgId(orgId, paginationDto); const finalResponse: IResponse = { @@ -293,21 +228,16 @@ export class OrganizationController { data: getInvitationById }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get all organizations - * @returns List of all organizations - */ + @Get('/') - @ApiOperation({ summary: 'Get all organizations', description: 'Retrieve a list of all organizations.' }) + @ApiOperation({ summary: 'Get all organizations', description: 'Get all organizations' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() - async getOrganizations( - @Query() organizationDto: GetAllOrganizationsDto, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async getOrganizations(@Query() organizationDto: GetAllOrganizationsDto, @Res() res: Response, @User() reqUser: user): Promise { + const getOrganizations = await this.organizationService.getOrganizations(organizationDto, reqUser.id); const finalResponse: IResponse = { @@ -316,23 +246,20 @@ export class OrganizationController { data: getOrganizations }; return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Get an organization by ID - * @param orgId The ID of the organization * @returns Organization details */ @Get('/:orgId') - @ApiOperation({ - summary: 'Get an organization', - description: 'Retrieve the details of a specific organization by its ID.' - }) + @ApiOperation({ summary: 'Get an organization', description: 'Get an organization by id' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) async getOrganization(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + const getOrganization = await this.organizationService.getOrganization(orgId, reqUser.id); const finalResponse: IResponse = { @@ -341,26 +268,16 @@ export class OrganizationController { data: getOrganization }; return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Fetch client credentials for an organization - * @param orgId The ID of the organization - * @returns Client credentials - */ + @Get('/:orgId/client_credentials') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiOperation({ - summary: 'Fetch client credentials for an organization', - description: 'Fetch client id and secret for an organization' - }) + @ApiOperation({ summary: 'Fetch client credentials for an organization', description: 'Fetch client id and secret for an organization' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async fetchOrgCredentials( - @Param('orgId') orgId: string, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async fetchOrgCredentials(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { const orgCredentials = await this.organizationService.fetchOrgCredentials(orgId, reqUser.id); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -371,20 +288,15 @@ export class OrganizationController { } /** - * Get organization users list - * @param orgId The ID of the organization - * @returns List of users in the organization - */ + * @returns Users list of organization + */ @Get('/:orgId/users') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.HOLDER, OrgRoles.ISSUER, OrgRoles.SUPER_ADMIN, OrgRoles.SUPER_ADMIN, OrgRoles.MEMBER) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Get organization users list', - description: 'Retrieve a list of users in a specific organization. Supports pagination and search.' - }) + @ApiOperation({ summary: 'Get organization users list', description: 'Get organization users list.' }) @ApiQuery({ name: 'pageNumber', type: Number, @@ -400,12 +312,7 @@ export class OrganizationController { type: String, required: false }) - async getOrganizationUsers( - @User() user: IUserRequestInterface, - @Query() paginationDto: PaginationDto, - @Param('orgId') orgId: string, - @Res() res: Response - ): Promise { + async getOrganizationUsers(@User() user: IUserRequestInterface, @Query() paginationDto: PaginationDto, @Param('orgId') orgId: string, @Res() res: Response): Promise { const users = await this.organizationService.getOrgUsers(orgId, paginationDto); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -416,49 +323,39 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Fetch organization DIDs - * @param orgId The ID of the organization - * @returns List of DIDs in the organization - */ - @Get('/:orgId/dids') - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.MEMBER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ - summary: 'Fetch organization DIDs', - description: 'Retrieve a list of all DIDs in a specific organization.' - }) - async getAllDidByOrgId(@Param('orgId') orgId: string, @Res() res: Response): Promise { - const users = await this.organizationService.getDidList(orgId); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.organisation.success.orgDids, - data: users - }; + /** + * @returns DID list of organization + */ + + @Get('/:orgId/dids') + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.MEMBER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiOperation({ summary: 'Fetch organization DIDs', description: 'Get all DIDs from organization' }) + + async getAllDidByOrgId(@Param('orgId') orgId: string, @Res() res: Response): Promise { + const users = await this.organizationService.getDidList(orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.organisation.success.orgDids, + data: users + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + +/** + * @returns organization details + */ - return res.status(HttpStatus.OK).json(finalResponse); - } - - /** - * Create a new organization - * @param createOrgDto The details of the organization to be created - * @returns Created organization details - */ @Post('/') - @ApiOperation({ - summary: 'Create a new Organization', - description: 'Create a new organization with the provided details.' - }) + @ApiOperation({ summary: 'Create a new Organization', description: 'Create an organization' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() - async createOrganization( - @Body() createOrgDto: CreateOrganizationDto, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async createOrganization(@Body() createOrgDto: CreateOrganizationDto, @Res() res: Response, @User() reqUser: user): Promise { + // eslint-disable-next-line prefer-destructuring const keycloakUserId = reqUser.keycloakUserId; @@ -471,23 +368,18 @@ export class OrganizationController { return res.status(HttpStatus.CREATED).json(finalResponse); } + /** - * Set primary DID for an organization - * @param orgId The ID of the organization - * @param primaryDidPayload The primary DID details - * @returns Success message - */ + * @returns success message + */ + @Put('/:orgId/primary-did') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) - @ApiOperation({ summary: 'Set primary DID', description: 'Set the primary DID for a specific organization.' }) + @ApiOperation({ summary: 'Set primary DID', description: 'Set primary DID for an organization' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async setPrimaryDid( - @Param('orgId') orgId: string, - @Body() primaryDidPayload: PrimaryDid, - @Res() res: Response - ): Promise { + async setPrimaryDid(@Param('orgId') orgId: string, @Body() primaryDidPayload: PrimaryDid, @Res() res: Response): Promise { await this.organizationService.setPrimaryDid(primaryDidPayload, orgId); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, @@ -496,29 +388,23 @@ export class OrganizationController { return res.status(HttpStatus.CREATED).json(finalResponse); } /** - * - * @param orgId - * @param res - * @param reqUser + * + * @param orgId + * @param res + * @param reqUser * @returns Organization Client Credentials */ @Post('/:orgId/client_credentials') @Roles(OrgRoles.OWNER) - @ApiOperation({ - summary: 'Create credentials for an organization', - description: 'Create client ID and secret for a specific organization.' - }) + @ApiOperation({ summary: 'Create credentials for an organization', description: 'Create client id and secret for an organization' }) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @ApiBearerAuth() - async createOrgCredentials( - @Param('orgId') orgId: string, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async createOrgCredentials(@Param('orgId') orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + // eslint-disable-next-line prefer-destructuring const keycloakUserId = reqUser.keycloakUserId; - + const orgCredentials = await this.organizationService.createOrgCredentials(orgId, reqUser.id, keycloakUserId); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, @@ -527,23 +413,14 @@ export class OrganizationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Authenticate client for credentials - * @param clientId The client ID - * @param clientCredentialsDto The client credentials details - * @returns Authenticated client credentials - */ + @Post('/:clientId/token') - @ApiOperation({ - summary: 'Authenticate client for credentials', - description: 'Authenticate client using the provided credentials.' - }) + @ApiOperation({ summary: 'Authenticate client for credentials', description: 'Authenticate client for credentials' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async clientLoginCredentials( @Param('clientId') clientId: string, @Body() clientCredentialsDto: ClientCredentialsDto, - @Res() res: Response - ): Promise { + @Res() res: Response): Promise { clientCredentialsDto.clientId = clientId.trim(); if (!clientCredentialsDto.clientId) { @@ -551,34 +428,25 @@ export class OrganizationController { } const orgCredentials = await this.organizationService.clientLoginCredentials(clientCredentialsDto); - const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.clientCredentials, data: orgCredentials }; - res.cookie('session_id', orgCredentials.sessionId, { - httpOnly: true, - sameSite: 'none', - secure: 'http' !== process.env.APP_PROTOCOL - }); - return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Register client and map users - * @returns Success message - */ + @Post('/register-org-map-users') @ApiOperation({ summary: 'Register client and map users', - description: 'Register a new client and map users to the client.' + description: 'Register client and map users' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.PLATFORM_ADMIN) @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async registerOrgsMapUsers(@Res() res: Response): Promise { + await this.organizationService.registerOrgsMapUsers(); const finalResponse: IResponse = { @@ -587,28 +455,20 @@ export class OrganizationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); + } - /** - * Create organization invitation - * @param bulkInvitationDto The details of the invitation - * @param orgId The ID of the organization - * @returns Success message - */ + @Post('/:orgId/invitations') @ApiOperation({ summary: 'Create organization invitation', - description: 'Create an invitation for a specific organization.' + description: 'Create organization invitation' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @Roles(OrgRoles.OWNER, OrgRoles.SUPER_ADMIN, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiBearerAuth() - async createInvitation( - @Body() bulkInvitationDto: BulkSendInvitationDto, - @Param('orgId') orgId: string, - @User() user: user, - @Res() res: Response - ): Promise { + async createInvitation(@Body() bulkInvitationDto: BulkSendInvitationDto, @Param('orgId') orgId: string, @User() user: user, @Res() res: Response): Promise { + bulkInvitationDto.orgId = orgId; await this.organizationService.createInvitation(bulkInvitationDto, user.id, user.email); @@ -618,35 +478,28 @@ export class OrganizationController { }; return res.status(HttpStatus.CREATED).json(finalResponse); + } - /** - * Update user roles - * @param updateUserDto The details of the user roles to be updated - * @param orgId The ID of the organization - * @param userId The ID of the user - * @returns Success message - */ +/** + * @returns organization details + */ @Put('/:orgId/user-roles/:userId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @ApiBearerAuth() @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiOperation({ summary: 'Update user roles', description: 'Update the roles of a user in a specific organization.' }) - async updateUserRoles( - @Body() updateUserDto: UpdateUserRolesDto, - @Param('orgId') orgId: string, - @Param('userId') userId: string, - @Res() res: Response - ): Promise { - updateUserDto.orgId = orgId; - updateUserDto.userId = userId.trim(); + @ApiOperation({ summary: 'Update user roles', description: 'update user roles' }) + async updateUserRoles(@Body() updateUserDto: UpdateUserRolesDto, @Param('orgId') orgId: string, @Param('userId') userId: string, @Res() res: Response): Promise { + + updateUserDto.orgId = orgId; + updateUserDto.userId = userId.trim(); if (!updateUserDto.userId.length) { throw new BadRequestException(ResponseMessages.organisation.error.userIdIsRequired); } if (!isValidUUID(updateUserDto.userId)) { throw new BadRequestException(ResponseMessages.organisation.error.invalidUserId); - } + } await this.organizationService.updateUserRoles(updateUserDto, updateUserDto.userId); @@ -657,14 +510,11 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Update an organization - * @param updateOrgDto The details of the organization to be updated - * @param orgId The ID of the organization - * @returns Success message - */ +/** + * @returns organization details + */ @Put('/:orgId') - @ApiOperation({ summary: 'Update Organization', description: 'Update the details of the organization.' }) + @ApiOperation({ summary: 'Update Organization', description: 'Update an organization' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @@ -673,20 +523,8 @@ export class OrganizationController { }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @UsePipes(new ValidationPipe()) - async updateOrganization( - @Body() updateOrgDto: UpdateOrganizationDto, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); - } - }) - ) - orgId: string, - @Res() res: Response, - @User() reqUser: user - ): Promise { + async updateOrganization(@Body() updateOrgDto: UpdateOrganizationDto, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, @Res() res: Response, @User() reqUser: user): Promise { + updateOrgDto.orgId = orgId; await this.organizationService.updateOrganization(updateOrgDto, reqUser.id, orgId); @@ -698,9 +536,7 @@ export class OrganizationController { } /** - * Delete an organization - * @param orgId The ID of the organization - * @returns Success message + * @returns Organization */ @Delete('/:orgId') @ApiOperation({ summary: 'Delete Organization', description: 'Delete an organization' }) @@ -709,19 +545,11 @@ export class OrganizationController { @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @Roles(OrgRoles.OWNER) async deleteOrganization( - @Param( - 'orgId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', TrimStringParamPipe, new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: user, @Res() res: Response - ): Promise { + ): Promise { + await this.organizationService.deleteOrganization(orgId, user); const finalResponse: IResponse = { @@ -731,22 +559,14 @@ export class OrganizationController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Delete organization client credentials - * @param orgId The ID of the organization - * @returns Success message - */ @Delete('/:orgId/client_credentials') - @ApiOperation({ summary: 'Delete Client Credentials', description: 'Delete Organization Client Credentials' }) + @ApiOperation({ summary: 'Delete Organization Client Credentials', description: 'Delete Organization Client Credentials' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @ApiExcludeEndpoint() @UseGuards(AuthGuard('jwt')) - async deleteOrgClientCredentials( - @Param('orgId') orgId: string, - @Res() res: Response, - @User() user: user - ): Promise { + async deleteOrgClientCredentials(@Param('orgId') orgId: string, @Res() res: Response, @User() user: user): Promise { + const deleteResponse = await this.organizationService.deleteOrgClientCredentials(orgId, user); const finalResponse: IResponse = { @@ -756,32 +576,27 @@ export class OrganizationController { return res.status(HttpStatus.ACCEPTED).json(finalResponse); } - /** - * Delete organization invitation - * @param orgId The ID of the organization - * @param invitationId The ID of the invitation - * @returns Success message - */ @Delete('/:orgId/invitations/:invitationId') - @ApiOperation({ summary: 'Delete invitation', description: 'Delete organization invitation' }) + @ApiOperation({ summary: 'Delete organization invitation', description: 'Delete organization invitation' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async deleteOrganizationInvitation( - @Param('orgId') orgId: string, - @Param('invitationId') invitationId: string, + @Param('orgId') orgId: string, + @Param('invitationId') invitationId: string, @Res() res: Response - ): Promise { - // eslint-disable-next-line no-param-reassign - invitationId = invitationId.trim(); - if (!invitationId.length) { - throw new BadRequestException(ResponseMessages.organisation.error.invitationIdIsRequired); - } + ): Promise { + // eslint-disable-next-line no-param-reassign + invitationId = invitationId.trim(); + if (!invitationId.length) { + throw new BadRequestException(ResponseMessages.organisation.error.invitationIdIsRequired); + } + + if (!isValidUUID(invitationId)) { + throw new BadRequestException(ResponseMessages.organisation.error.invalidInvitationId); + } - if (!isValidUUID(invitationId)) { - throw new BadRequestException(ResponseMessages.organisation.error.invalidInvitationId); - } await this.organizationService.deleteOrganizationInvitation(orgId, invitationId); const finalResponse: IResponse = { @@ -790,16 +605,5 @@ export class OrganizationController { }; return res.status(HttpStatus.OK).json(finalResponse); } - - @Post('/generateIssuerApiToken') - @ApiExcludeEndpoint() - @ApiOperation({ - summary: 'Generate API Token for the issuer', - description: 'Generate API Token for the issuer' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - async generateApiToken(@Body() clientTokenDto: ClientTokenDto, @Res() res: Response): Promise { - const finalResponse = await this.organizationService.generateClientApiToken(clientTokenDto); - return res.status(HttpStatus.OK).header('Content-Type', 'application/json').send(finalResponse); - } } + diff --git a/apps/api-gateway/src/organization/organization.module.ts b/apps/api-gateway/src/organization/organization.module.ts index bc8da3334..bcf2f6c53 100644 --- a/apps/api-gateway/src/organization/organization.module.ts +++ b/apps/api-gateway/src/organization/organization.module.ts @@ -7,9 +7,9 @@ import { Module } from '@nestjs/common'; import { OrganizationController } from './organization.controller'; import { OrganizationService } from './organization.service'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { ImageServiceService } from '@credebl/image-service'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ HttpModule, @@ -19,11 +19,13 @@ import { NATSClient } from '@credebl/common/NATSClient'; name: 'NATS_CLIENT', transport: Transport.NATS, options: getNatsOptions(CommonConstants.ORGANIZATION_SERVICE, process.env.API_GATEWAY_NKEY_SEED) + }, CommonModule ]) ], controllers: [OrganizationController], - providers: [OrganizationService, CommonService, AwsService, NATSClient] + providers: [OrganizationService, CommonService, ImageServiceService, AwsService] }) -export class OrganizationModule {} +export class OrganizationModule { } + diff --git a/apps/api-gateway/src/organization/organization.service.ts b/apps/api-gateway/src/organization/organization.service.ts index 72e1c89b4..88b00b0de 100644 --- a/apps/api-gateway/src/organization/organization.service.ts +++ b/apps/api-gateway/src/organization/organization.service.ts @@ -1,5 +1,6 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CreateOrganizationDto } from './dtos/create-organization-dto'; import { BulkSendInvitationDto } from './dtos/send-invitation.dto'; @@ -8,30 +9,17 @@ import { UpdateOrganizationDto } from './dtos/update-organization-dto'; import { organisation, user } from '@prisma/client'; import { IDidList, IGetOrgById, IGetOrganization } from 'apps/organization/interfaces/organization.interface'; import { IOrgUsers } from 'apps/user/interfaces/user.interface'; -import { - IOrgCredentials, - IOrganization, - IOrganizationInvitations, - IOrganizationDashboard, - IDeleteOrganization, - IOrgActivityCount -} from '@credebl/common/interfaces/organization.interface'; +import { IOrgCredentials, IOrganization, IOrganizationInvitations, IOrganizationDashboard, IDeleteOrganization, IOrgActivityCount } from '@credebl/common/interfaces/organization.interface'; import { ClientCredentialsDto } from './dtos/client-credentials.dto'; import { IAccessTokenData } from '@credebl/common/interfaces/interface'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface'; import { GetAllOrganizationsDto } from './dtos/get-organizations.dto'; import { PrimaryDid } from './dtos/set-primary-did.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; -import { ClientTokenDto } from './dtos/client-token.dto'; @Injectable() export class OrganizationService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('OrganizationService'); } @@ -40,13 +28,9 @@ export class OrganizationService extends BaseService { * @param createOrgDto * @returns Organization creation Success */ - async createOrganization( - createOrgDto: CreateOrganizationDto, - userId: string, - keycloakUserId: string - ): Promise { + async createOrganization(createOrgDto: CreateOrganizationDto, userId: string, keycloakUserId: string): Promise { const payload = { createOrgDto, userId, keycloakUserId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'create-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'create-organization', payload); } /** @@ -54,21 +38,21 @@ export class OrganizationService extends BaseService { * @param primaryDidPayload * @returns Set Primary Did for organization */ - async setPrimaryDid(primaryDidPayload: PrimaryDid, orgId: string): Promise { - const { did, id } = primaryDidPayload; - const payload = { did, orgId, id }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'set-primary-did', payload); + async setPrimaryDid(primaryDidPayload: PrimaryDid, orgId:string): Promise { + const {did, id} = primaryDidPayload; + const payload = { did, orgId, id}; + return this.sendNatsMessage(this.serviceProxy, 'set-primary-did', payload); } /** - * - * @param orgId - * @param userId + * + * @param orgId + * @param userId * @returns Orgnization client credentials */ async createOrgCredentials(orgId: string, userId: string, keycloakUserId: string): Promise { const payload = { orgId, userId, keycloakUserId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'create-org-credentials', payload); + return this.sendNatsMessage(this.serviceProxy, 'create-org-credentials', payload); } /** @@ -78,16 +62,16 @@ export class OrganizationService extends BaseService { */ async updateOrganization(updateOrgDto: UpdateOrganizationDto, userId: string, orgId: string): Promise { const payload = { updateOrgDto, userId, orgId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-organization', payload); } /** - * - * @param orgId + * + * @param orgId * @returns Organization details with owner */ async findOrganizationOwner(orgId: string): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organization-owner', orgId); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-owner', orgId); } /** @@ -98,10 +82,10 @@ export class OrganizationService extends BaseService { async getOrganizations(organizationDto: GetAllOrganizationsDto, userId: string): Promise { const payload = { userId, ...organizationDto }; - const fetchOrgs = await this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organizations', payload); + const fetchOrgs = await this.sendNatsMessage(this.serviceProxy, 'get-organizations', payload); return fetchOrgs; } - + /** * * @param @@ -109,14 +93,14 @@ export class OrganizationService extends BaseService { */ async getPublicOrganizations(paginationDto: PaginationDto): Promise { const payload = { ...paginationDto }; - const PublicOrg = this.natsClient.sendNatsMessage(this.serviceProxy, 'get-public-organizations', payload); + const PublicOrg = this.sendNatsMessage(this.serviceProxy, 'get-public-organizations', payload); return PublicOrg; } async getPublicProfile(orgSlug: string): Promise { const payload = { orgSlug }; try { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organization-public-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-public-profile', payload); } catch (error) { this.logger.error(`Error in get user:${JSON.stringify(error)}`); } @@ -129,12 +113,12 @@ export class OrganizationService extends BaseService { */ async getOrganization(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organization-by-id', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-by-id', payload); } async fetchOrgCredentials(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-org-client-credentials', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-org-client-credentials', payload); } /** @@ -142,20 +126,23 @@ export class OrganizationService extends BaseService { * @param orgId * @returns Invitations details */ - async getInvitationsByOrgId(orgId: string, pagination: PaginationDto): Promise { + async getInvitationsByOrgId( + orgId: string, + pagination: PaginationDto + ): Promise { const { pageNumber, pageSize, search } = pagination; const payload = { orgId, pageNumber, pageSize, search }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-invitations-by-orgId', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-invitations-by-orgId', payload); } - + async getOrganizationDashboard(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organization-dashboard', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-dashboard', payload); } async getOrganizationActivityCount(orgId: string, userId: string): Promise { const payload = { orgId, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-organization-activity-count', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-organization-activity-count', payload); } /** @@ -165,8 +152,8 @@ export class OrganizationService extends BaseService { */ async getOrgRoles(orgId: string, user: user): Promise { - const payload = { orgId, user }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-org-roles', payload); + const payload = {orgId, user}; + return this.sendNatsMessage(this.serviceProxy, 'get-org-roles', payload); } /** @@ -176,12 +163,12 @@ export class OrganizationService extends BaseService { */ async createInvitation(bulkInvitationDto: BulkSendInvitationDto, userId: string, userEmail: string): Promise { const payload = { bulkInvitationDto, userId, userEmail }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'send-invitation', payload); + return this.sendNatsMessage(this.serviceProxy, 'send-invitation', payload); } async registerOrgsMapUsers(): Promise { const payload = {}; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'register-orgs-users-map', payload); + return this.sendNatsMessage(this.serviceProxy, 'register-orgs-users-map', payload); } /** @@ -192,54 +179,64 @@ export class OrganizationService extends BaseService { */ async updateUserRoles(updateUserDto: UpdateUserRolesDto, userId: string): Promise { const payload = { orgId: updateUserDto.orgId, roleIds: updateUserDto.orgRoleId, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-user-roles', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-user-roles', payload); } - async getOrgUsers(orgId: string, paginationDto: PaginationDto): Promise { + async getOrgUsers( + orgId: string, + paginationDto: PaginationDto + ): Promise { const { pageNumber, pageSize, search } = paginationDto; const payload = { orgId, pageNumber, pageSize, search }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-organization-user', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-organization-user', payload); } - async getDidList(orgId: string): Promise { + async getDidList( + orgId: string + ): Promise { const payload = { orgId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-organization-dids', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-organization-dids', payload); } - async getOrgPofile(orgId: string): Promise { + async getOrgPofile( + orgId: string + ): Promise { const payload = { orgId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-organization-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-organization-profile', payload); } - async deleteOrganization(orgId: string, user: user): Promise { + async deleteOrganization( + orgId: string, + user: user + ): Promise { const payload = { orgId, user }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-organization', payload); + return this.sendNatsMessage(this.serviceProxy, 'delete-organization', payload); } - async deleteOrgClientCredentials(orgId: string, user: user): Promise { + async deleteOrgClientCredentials( + orgId: string, + user: user + ): Promise { const payload = { orgId, user }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-org-client-credentials', payload); + return this.sendNatsMessage(this.serviceProxy, 'delete-org-client-credentials', payload); } - async deleteOrganizationInvitation(orgId: string, invitationId: string): Promise { - const payload = { orgId, invitationId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'delete-organization-invitation', payload); + async deleteOrganizationInvitation( + orgId: string, + invitationId: string + ): Promise { + const payload = {orgId, invitationId}; + return this.sendNatsMessage(this.serviceProxy, 'delete-organization-invitation', payload); } - async clientLoginCredentials(clientCredentialsDto: ClientCredentialsDto): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'authenticate-client-credentials', clientCredentialsDto); + async clientLoginCredentials( + clientCredentialsDto: ClientCredentialsDto + ): Promise { + return this.sendNatsMessage(this.serviceProxy, 'authenticate-client-credentials', clientCredentialsDto); } - getBase64Image(base64Image: string): Buffer { - const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, ''); - const imageBuffer = Buffer.from(base64Data, 'base64'); - return imageBuffer; - } - async generateClientApiToken(clientTokenDto: ClientTokenDto): Promise<{ token: string }> { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'generate-client-api-token', clientTokenDto); - } } diff --git a/apps/api-gateway/src/platform/platform.controller.spec.ts b/apps/api-gateway/src/platform/platform.controller.spec.ts index 8cb1a53a9..a9e62b8c8 100644 --- a/apps/api-gateway/src/platform/platform.controller.spec.ts +++ b/apps/api-gateway/src/platform/platform.controller.spec.ts @@ -1,6 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Test, TestingModule } from '@nestjs/testing'; -import { plainToClass } from 'class-transformer'; -import { validate } from 'class-validator'; import { PlatformController } from './platform.controller'; import { PlatformService } from './platform.service'; import { SortValue } from './platform.model'; diff --git a/apps/api-gateway/src/platform/platform.controller.ts b/apps/api-gateway/src/platform/platform.controller.ts index 3f1c2b35f..980acfb02 100644 --- a/apps/api-gateway/src/platform/platform.controller.ts +++ b/apps/api-gateway/src/platform/platform.controller.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Controller, Get, HttpStatus, Logger, Param, Query, Res, UseFilters, UseGuards } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Logger, Param, Query, Res, UseFilters, UseGuards } from '@nestjs/common'; import { PlatformService } from './platform.service'; import { ApiBearerAuth, ApiExcludeEndpoint, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -14,7 +14,6 @@ import { AuthGuard } from '@nestjs/passport'; import * as QRCode from 'qrcode'; import { CredDefSortFields, SchemaType, SortFields } from '@credebl/enum/enum'; import { GetAllPlatformCredDefsDto } from '../credential-definition/dto/get-all-platform-cred-defs.dto'; -import { TrimStringParamPipe } from '@credebl/common/cast.helper'; @Controller('') @UseFilters(CustomExceptionFilter) @@ -23,19 +22,11 @@ export class PlatformController { private readonly logger = new Logger('PlatformController'); - /** - * Retrieves all schemas available on the platform with optional filters and sorting. - * - * @param ledgerId The ID of the ledger. - * @param schemaType Type of schema to filter results. - * - * @returns A paginated list of schemas based on the provided criteria. - */ @Get('/platform/schemas') @ApiTags('schemas') @ApiOperation({ summary: 'Get all schemas from platform.', - description: 'Retrieves all schemas available on the platform' + description: 'Get all schemas from platform.' }) @ApiQuery({ name: 'sortField', @@ -74,16 +65,12 @@ export class PlatformController { return res.status(HttpStatus.OK).json(finalResponse); } -/** - * Retrieves all credential definitions available on the platform. - * - * @returns A list of credential definitions and their details. - */ + @Get('/platform/cred-defs') @ApiTags('credential-definitions') @ApiOperation({ summary: 'Get all credential-definitions from platform.', - description: 'Retrieves all credential definitions available on the platform' + description: 'Get all credential-definitions list from platform.' }) @ApiQuery({ name: 'sortField', @@ -107,16 +94,12 @@ export class PlatformController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieves all available ledgers from the platform. - * - * @returns A list of ledgers and their details. - */ + @Get('/platform/ledgers') @ApiTags('ledgers') @ApiOperation({ summary: 'Get all ledgers from platform.', - description: 'Retrieves a list of all available ledgers on platform.' + description: 'Get all ledgers from platform.' }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @@ -134,28 +117,19 @@ export class PlatformController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieves the network URL associated with a specific ledger namespace. - * - * @param indyNamespace The namespace of the ledger. - * @returns The network URL for the specified ledger. - */ @Get('/platform/network/url/:indyNamespace') @ApiTags('ledgers') @ApiOperation({ summary: 'Get network url from platform.', - description: 'Retrieves the network URL for a specified ledger namespace.' + description: 'Get network url from platform.' }) @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getNetwrkUrl( - @Param('indyNamespace', TrimStringParamPipe) indyNamespace: string, + @Param('indyNamespace') indyNamespace: string, @Res() res: Response ): Promise { - if (!indyNamespace) { - throw new BadRequestException(ResponseMessages.ledger.error.indyNamespaceisRequired); - } const networksResponse = await this.platformService.getNetworkUrl(indyNamespace); const finalResponse: IResponse = { diff --git a/apps/api-gateway/src/platform/platform.module.ts b/apps/api-gateway/src/platform/platform.module.ts index d169ee4cf..f5b3119c1 100644 --- a/apps/api-gateway/src/platform/platform.module.ts +++ b/apps/api-gateway/src/platform/platform.module.ts @@ -5,7 +5,7 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; + @Module({ imports: [ ConfigModule.forRoot(), @@ -18,6 +18,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [PlatformController], - providers: [PlatformService, NATSClient] + providers: [PlatformService] }) export class PlatformModule {} diff --git a/apps/api-gateway/src/platform/platform.service.ts b/apps/api-gateway/src/platform/platform.service.ts index 8fdc9865e..6bfa64dfd 100644 --- a/apps/api-gateway/src/platform/platform.service.ts +++ b/apps/api-gateway/src/platform/platform.service.ts @@ -1,46 +1,43 @@ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; import { ILedgers, ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import { IUserRequestInterface } from '../interfaces/IUserRequestInterface'; import { INetworkUrl, ISchemaDetails } from '@credebl/common/interfaces/schema.interface'; import { GetAllPlatformCredDefsDto } from '../credential-definition/dto/get-all-platform-cred-defs.dto'; import { IPlatformCredDefsData } from '@credebl/common/interfaces/cred-def.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class PlatformService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly platformServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('PlatformService'); - } + constructor( + @Inject('NATS_CLIENT') private readonly platformServiceProxy: ClientProxy + ) { + super('PlatformService'); + } + + async getAllSchema(schemaSearchCriteria: ISchemaSearchPayload, user: IUserRequestInterface): Promise { + const schemaSearch = { schemaSearchCriteria, user }; + return this.sendNatsMessage(this.platformServiceProxy, 'get-all-schemas', schemaSearch); - async getAllSchema(schemaSearchCriteria: ISchemaSearchPayload, user: IUserRequestInterface): Promise { - const schemaSearch = { schemaSearchCriteria, user }; - return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-all-schemas', schemaSearch); - } + } - async getAllPlatformCredDefs( - getAllPlatformCredDefs: GetAllPlatformCredDefsDto, - user: IUserRequestInterface - ): Promise { - const credDefs = { ...getAllPlatformCredDefs, user }; - return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-all-platform-cred-defs', credDefs); - } + async getAllPlatformCredDefs(getAllPlatformCredDefs: GetAllPlatformCredDefsDto, user: IUserRequestInterface): Promise { + const credDefs = { ...getAllPlatformCredDefs, user }; + return this.sendNatsMessage(this.platformServiceProxy, 'get-all-platform-cred-defs', credDefs); + } - async getAllLedgers(): Promise { - const payload = {}; - return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-all-ledgers', payload); - } + async getAllLedgers(): Promise { + const payload = {}; + return this.sendNatsMessage(this.platformServiceProxy, 'get-all-ledgers', payload); + } - async getNetworkUrl(indyNamespace: string): Promise { - return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-network-url', indyNamespace); - } + async getNetworkUrl(indyNamespace: string): Promise { + return this.sendNatsMessage(this.platformServiceProxy, 'get-network-url', indyNamespace); + } - async getShorteningUrlById(referenceId: string): Promise { - // NATS call - return this.natsClient.sendNatsMessage(this.platformServiceProxy, 'get-shortening-url', referenceId); - } -} + async getShorteningUrlById(referenceId: string): Promise { + + // NATS call + return this.sendNatsMessage(this.platformServiceProxy, 'get-shortening-url', referenceId); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/revocation/revocation.module.ts b/apps/api-gateway/src/revocation/revocation.module.ts index 77558663b..be0cbc544 100644 --- a/apps/api-gateway/src/revocation/revocation.module.ts +++ b/apps/api-gateway/src/revocation/revocation.module.ts @@ -4,19 +4,20 @@ import { ClientsModule } from '@nestjs/microservices'; import { RevocationService } from './revocation.service'; import { RevocationController } from './revocation.controller'; import { commonNatsOptions } from 'libs/service/nats.options'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ - imports: [ - ConfigModule.forRoot(), - ClientsModule.register([ - { - name: 'NATS_CLIENT', - ...commonNatsOptions('REVOCATION_SERVICE:REQUESTER') - } - ]) - ], - controllers: [RevocationController], - providers: [RevocationService, NATSClient] + imports: [ + ConfigModule.forRoot(), + ClientsModule.register([ + { + name: 'NATS_CLIENT', + ...commonNatsOptions('REVOCATION_SERVICE:REQUESTER') + } + ]) + ], + controllers: [RevocationController], + providers: [RevocationService] }) -export class RevocationModule {} +export class RevocationModule { + +} diff --git a/apps/api-gateway/src/revocation/revocation.service.ts b/apps/api-gateway/src/revocation/revocation.service.ts index d6e8cfb9f..54ce9b473 100644 --- a/apps/api-gateway/src/revocation/revocation.service.ts +++ b/apps/api-gateway/src/revocation/revocation.service.ts @@ -1,49 +1,49 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { CreateRevocationRegistryDto } from '../dtos/create-revocation-registry.dto'; import { UpdateRevocationRegistryUriDto } from '../dtos/update-revocation-registry.dto'; import { BaseService } from 'libs/service/base.service'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; + @Injectable() export class RevocationService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly revocationServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('RevocationService'); - } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - createRevocationRegistry(createRevocationRegistryDto: CreateRevocationRegistryDto, user: any) { - this.logger.log('**** createRevocationRegistry called'); - const payload = { createRevocationRegistryDto, user }; - return this.natsClient.sendNats(this.revocationServiceProxy, 'create-revocation-registry', payload); - } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - updateRevocationRegistryUri(updateRevocationRegistryUriDto: UpdateRevocationRegistryUriDto, user: any) { - this.logger.log('**** updateRevocationRegistryUri called'); - const payload = { updateRevocationRegistryUriDto, user }; - return this.natsClient.sendNats(this.revocationServiceProxy, 'update-revocation-registry-uri', payload); - } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - activeRevocationRegistry(cred_def_id: string, user: any) { - this.logger.log('**** activeRevocationRegistry called'); - const payload = { cred_def_id, user }; - return this.natsClient.sendNats(this.revocationServiceProxy, 'active-revocation-registry', payload); - } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - publishRevocationRegistry(revocationId: string, user: any) { - this.logger.log('**** publishRevocationRegistry called'); - const payload = { revocationId, user }; - return this.natsClient.sendNats(this.revocationServiceProxy, 'publish-revocation-registry', payload); - } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - getRevocationRegistry(rev_reg_id: string, user: any) { - this.logger.log('**** getRevocationRegistry called'); - const payload = { rev_reg_id, user }; - return this.natsClient.sendNats(this.revocationServiceProxy, 'get-revocation-registry', payload); - } -} + constructor( + @Inject('NATS_CLIENT') private readonly revocationServiceProxy: ClientProxy + ) { + super('RevocationService'); + } + + createRevocationRegistry(createRevocationRegistryDto: CreateRevocationRegistryDto, user: any) { + this.logger.log('**** createRevocationRegistryDto called'); + const payload = { createRevocationRegistryDto, user }; + return this.sendNats(this.revocationServiceProxy, 'create-revocation-registry', payload); + } + + updateRevocationRegistryUri(updateRevocationRegistryUriDto: UpdateRevocationRegistryUriDto, user: any) { + this.logger.log('**** updateRevocationRegistryUri called'); + const payload = { updateRevocationRegistryUriDto, user }; + return this.sendNats(this.revocationServiceProxy, 'update-revocation-registry-uri', payload); + } + + activeRevocationRegistry(cred_def_id: string, user: any) { + this.logger.log('**** activeRevocationRegistry called'); + const payload = { cred_def_id, user }; + return this.sendNats(this.revocationServiceProxy, 'active-revocation-registry', payload); + } + + publishRevocationRegistry(revocationId: string, user: any) { + this.logger.log('**** publishRevocationRegistry called'); + const payload = { revocationId, user }; + return this.sendNats(this.revocationServiceProxy, 'publish-revocation-registry', payload); + } + + getRevocationRegistry(rev_reg_id: string, user: any) { + this.logger.log('**** getRevocationRegistry called'); + const payload = { rev_reg_id, user }; + return this.sendNats(this.revocationServiceProxy, 'get-revocation-registry', payload); + } +} \ No newline at end of file diff --git a/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts b/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts index ff00b52c2..797f924cf 100644 --- a/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts +++ b/apps/api-gateway/src/schema/dtos/get-all-schema.dto.ts @@ -7,126 +7,128 @@ import { toNumber, trim } from '@credebl/common/cast.helper'; import { CredDefSortFields, SchemaType, SortFields, SortValue } from '@credebl/enum/enum'; export class GetAllSchemaDto { - @ApiProperty({ required: false }) - @IsOptional() - @Transform(({ value }) => trim(value)) - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ required: false, default: 1 }) - @IsOptional() - @Transform(({ value }) => toNumber(value)) - @Min(1, { message: 'Page number must be greater than 0' }) - pageNumber: number = 1; - - @ApiProperty({ required: false, default: 10 }) - @IsOptional() - @Transform(({ value }) => toNumber(value)) - @Min(1, { message: 'Page size must be greater than 0' }) - pageSize: number = 10; - - @ApiProperty({ - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortFields) - sortField: string = SortFields.CREATED_DATE_TIME; - - @ApiProperty({ - enum: [SortValue.DESC, SortValue.ASC], - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortValue) - sortBy: string = SortValue.DESC; + @ApiProperty({ required: false }) + @IsOptional() + @Transform(({ value }) => trim(value)) + @Type(() => String) + searchByText: string = ''; + + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page number must be greater than 0' }) + pageNumber: number = 1; + + @ApiProperty({ required: false, default: 10 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page size must be greater than 0' }) + pageSize: number = 10; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortFields) + sortField: string = SortFields.CREATED_DATE_TIME; + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; } export class GetCredentialDefinitionBySchemaIdDto { - @ApiProperty({ required: false, default: 1 }) - @IsOptional() - @Transform(({ value }) => toNumber(value)) - @Min(1, { message: 'Page number must be greater than 0' }) - pageNumber: number = 1; - - @ApiProperty({ required: false, default: 10 }) - @IsOptional() - @Transform(({ value }) => toNumber(value)) - @Min(1, { message: 'Page size must be greater than 0' }) - pageSize: number = 10; - - @ApiProperty({ required: false }) - @IsOptional() - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ - required: false, - enum: CredDefSortFields - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(CredDefSortFields) - sortField: string = CredDefSortFields.CREATED_DATE_TIME; - - @ApiProperty({ - enum: [SortValue.DESC, SortValue.ASC], - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortValue) - sortBy: string = SortValue.DESC; - - schemaId: string; - - orgId: string; + + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page number must be greater than 0' }) + pageNumber: number = 1; + + @ApiProperty({ required: false, default: 10 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page size must be greater than 0' }) + pageSize: number = 10; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + searchByText: string = ''; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(CredDefSortFields) + sortField: string = SortFields.CREATED_DATE_TIME; + + @ApiProperty({ + enum: [SortValue.DESC, SortValue.ASC], + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortValue) + sortBy: string = SortValue.DESC; + + schemaId: string; + + orgId: string; } + export class GetAllSchemaByPlatformDto { - @ApiProperty({ example: '1a7eac11-ff05-40d7-8351-4d7467687cad' }) - @ApiPropertyOptional() - @Transform(({ value }) => ('string' === typeof value && '' === value.trim() ? undefined : value.trim())) - @IsOptional() - @IsUUID('4', { message: 'Invalid format of ledgerId' }) - ledgerId?: string; - - @ApiProperty({ required: false, default: 1 }) - @IsOptional() - @Transform(({ value }) => toNumber(value, { min: 1 })) - @Min(1, { message: 'Page number must be greater than 0' }) - pageNumber: number = 1; - - @ApiProperty({ required: false }) - @IsOptional() - @Type(() => String) - searchByText: string = ''; - - @ApiProperty({ required: false, default: 10 }) - @IsOptional() - @Transform(({ value }) => toNumber(value, { min: 1 })) - @Min(1, { message: 'Page size must be greater than 0' }) - pageSize: number = 10; - - @ApiProperty({ - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SortFields) - sorting: string = SortFields.CREATED_DATE_TIME; - - @ApiProperty({ required: false }) - @IsOptional() - sortByValue: string = SortValue.DESC; - - @ApiProperty({ - enum: SchemaType, - required: false - }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsEnum(SchemaType) - schemaType: SchemaType; -} + + @ApiProperty({ example: '1a7eac11-ff05-40d7-8351-4d7467687cad'}) + @ApiPropertyOptional() + @IsOptional() + @IsUUID('4', { message: 'Invalid format of ledgerId' }) + ledgerId?: string; + + @ApiProperty({ required: false, default: 1 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page number must be greater than 0' }) + pageNumber: number = 1; + + @ApiProperty({ required: false }) + @IsOptional() + @Type(() => String) + searchByText: string = ''; + + @ApiProperty({ required: false, default: 10 }) + @IsOptional() + @Transform(({ value }) => toNumber(value)) + @Min(1, { message: 'Page size must be greater than 0' }) + pageSize: number = 10; + + @ApiProperty({ + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SortFields) + sorting: string = SortFields.CREATED_DATE_TIME; + + @ApiProperty({ required: false }) + @IsOptional() + sortByValue: string = SortValue.DESC; + + @ApiProperty({ + type: SchemaType, + required: false + }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsEnum(SchemaType) + schemaType: SchemaType; + +} \ No newline at end of file diff --git a/apps/api-gateway/src/schema/dtos/update-schema-dto.ts b/apps/api-gateway/src/schema/dtos/update-schema-dto.ts deleted file mode 100644 index 80fe4f876..000000000 --- a/apps/api-gateway/src/schema/dtos/update-schema-dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; - -export class UpdateSchemaDto { - @ApiProperty() - @Transform(({ value }) => value?.trim()) - @IsNotEmpty({ message: 'Alias is required.' }) - @IsString({ message: 'Alias must be in string format.' }) - alias: string; - - @ApiProperty() - @Transform(({ value }) => value?.trim()) - @IsNotEmpty({ message: 'schemaLedgerId is required.' }) - @IsString({ message: 'schemaLedgerId must be in string format.' }) - schemaLedgerId: string; - - @ApiPropertyOptional() - @IsOptional() - @Transform(({ value }) => value?.trim()) - @IsUUID('4') - orgId?: string; -} diff --git a/apps/api-gateway/src/schema/schema.controller.spec.ts b/apps/api-gateway/src/schema/schema.controller.spec.ts index 6e0d602bc..6a477f076 100644 --- a/apps/api-gateway/src/schema/schema.controller.spec.ts +++ b/apps/api-gateway/src/schema/schema.controller.spec.ts @@ -1,6 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable camelcase */ import { Test, TestingModule } from '@nestjs/testing'; - -import { Any } from 'typeorm'; import { CreateSchemaDto } from '../dtos/create-schema.dto'; import { SchemaController } from './schema.controller'; import { SchemaService } from './schema.service'; @@ -32,7 +32,7 @@ describe('schemaController Test Cases', () => { }); /////////////////////--------Create Schema -------------------////////////////////// describe('createSchema', () => { - const user: any = {}; + const user: unknown = {}; it('should return an expected schema', async () => { const createSchemaDto: CreateSchemaDto = new CreateSchemaDto; @@ -164,7 +164,6 @@ describe('schemaController Test Cases', () => { /////////////////////////----- get schemas with organization id ---------- describe('getSchemasByOrgId', () => { - const user: any = {}; const page = 1; const search_text = 'test search'; const items_per_page = 1; diff --git a/apps/api-gateway/src/schema/schema.controller.ts b/apps/api-gateway/src/schema/schema.controller.ts index 8373e8aa2..4d5bcf75e 100644 --- a/apps/api-gateway/src/schema/schema.controller.ts +++ b/apps/api-gateway/src/schema/schema.controller.ts @@ -1,31 +1,7 @@ -import { - Controller, - Logger, - Post, - Body, - HttpStatus, - UseGuards, - Get, - Query, - BadRequestException, - Res, - UseFilters, - Param, - ParseUUIDPipe, - Put -} from '@nestjs/common'; +import { Controller, Logger, Post, Body, HttpStatus, UseGuards, Get, Query, BadRequestException, Res, UseFilters, Param, ParseUUIDPipe } from '@nestjs/common'; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable camelcase */ -import { - ApiOperation, - ApiResponse, - ApiTags, - ApiBearerAuth, - ApiForbiddenResponse, - ApiUnauthorizedResponse, - ApiQuery, - ApiExcludeEndpoint -} from '@nestjs/swagger'; +import { ApiOperation, ApiResponse, ApiTags, ApiBearerAuth, ApiForbiddenResponse, ApiUnauthorizedResponse, ApiQuery, ApiExtraModels, ApiBody, getSchemaPath } from '@nestjs/swagger'; import { SchemaService } from './schema.service'; import { AuthGuard } from '@nestjs/passport'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; @@ -45,42 +21,35 @@ import { GenericSchemaDTO } from '../dtos/create-schema.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { CredDefSortFields, SortFields } from '@credebl/enum/enum'; import { TrimStringParamPipe } from '@credebl/common/cast.helper'; -import { UpdateSchemaDto } from './dtos/update-schema-dto'; @UseFilters(CustomExceptionFilter) @Controller('orgs') @ApiTags('schemas') @ApiBearerAuth() -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class SchemaController { - constructor(private readonly appService: SchemaService) {} + constructor(private readonly appService: SchemaService + ) { } private readonly logger = new Logger('SchemaController'); - /** - * Retrieves schema information from the ledger using its schema ID. - * - * @param orgId The organization ID. - * @param schemaId The unique schema ID. - * @returns The schema details retrieved from the ledger. - */ @Get('/:orgId/schemas/:schemaId') @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiOperation({ summary: 'Get schema information from the ledger using its schema ID.', - description: 'Retrives schema information from the ledger using its schema ID.' + description: 'Get schema information from the ledger using its schema ID.' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getSchemaById( @Res() res: Response, - @Param('orgId') orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Param('schemaId', TrimStringParamPipe) schemaId: string ): Promise { + if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } - const schemaDetails = await this.appService.getSchemaById(schemaId, orgId); const finalResponse: IResponse = { statusCode: HttpStatus.OK, @@ -90,19 +59,10 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieves a list of credential definitions associated with a given schema ID. - * - * @param orgId The organization ID. - * @param schemaId The unique schema ID. - * @param sortField The field by which to sort the results (optional). - * - * @returns A list of credential definitions filtered by schema ID. - */ @Get('/:orgId/schemas/:schemaId/cred-defs') @ApiOperation({ summary: 'Credential definitions by schema Id', - description: 'Retrives credential definition list by schema Id available on platform.' + description: 'Get credential definition list by schema Id' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiQuery({ @@ -113,20 +73,12 @@ export class SchemaController { @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) async getcredDeffListBySchemaId( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @Param('schemaId', TrimStringParamPipe) schemaId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, + @Param('schemaId') schemaId: string, @Query() getCredentialDefinitionBySchemaIdDto: GetCredentialDefinitionBySchemaIdDto, @Res() res: Response, - @User() user: IUserRequestInterface - ): Promise { + @User() user: IUserRequestInterface): Promise { + if (!schemaId) { throw new BadRequestException(ResponseMessages.schema.error.invalidSchemaId); } @@ -134,30 +86,20 @@ export class SchemaController { getCredentialDefinitionBySchemaIdDto.schemaId = schemaId; getCredentialDefinitionBySchemaIdDto.orgId = orgId; - const credentialDefinitionList = await this.appService.getcredDefListBySchemaId( - getCredentialDefinitionBySchemaIdDto, - user - ); + const credentialDefinitionList = await this.appService.getcredDefListBySchemaId(getCredentialDefinitionBySchemaIdDto, user); const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.schema.success.fetch, data: credentialDefinitionList }; - + return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieves a list of schemas associated with a given organization ID. - * - * @param orgId The organization ID. - * - * @returns A list of schemas filtered by organization ID. - */ @Get('/:orgId/schemas') @ApiOperation({ summary: 'Schemas by org id.', - description: 'Retrieves all schemas belonging to a specific organization available on platform.' + description: 'Get all schemas by org id.' }) @ApiQuery({ name: 'sortField', @@ -169,18 +111,11 @@ export class SchemaController { @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) async getSchemas( @Query() getAllSchemaDto: GetAllSchemaDto, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response, @User() user: IUserRequestInterface ): Promise { + const { pageSize, searchByText, pageNumber, sortField, sortBy } = getAllSchemaDto; const schemaSearchCriteria: ISchemaSearchPayload = { pageNumber, @@ -199,37 +134,18 @@ export class SchemaController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Create and register various types of schemas. - * - * @param orgId The organization ID. - * @param schemaDetails The schema details. - * @returns The created schema details. - */ + @Post('/:orgId/schemas') @ApiOperation({ summary: 'Create and register various types of schemas.', - description: - 'Create and register a schema for an organization. Supports multiple systems like Indy, Polygon, and W3C standards.' - }) + description: 'Enables the creation and registration of schemas across different systems: the Indy ledger, the Polygon blockchain network, and W3C ledger-less standards.' + } + ) @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) @UseGuards(AuthGuard('jwt'), OrgRolesGuard) @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - async createSchema( - @Res() res: Response, - @Body() schemaDetails: GenericSchemaDTO, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @User() user: IUserRequestInterface - ): Promise { - const schemaResponse = await this.appService.createSchema(schemaDetails, user, orgId); + async createSchema(@Res() res: Response, @Body() schemaDetails: GenericSchemaDTO, @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: IUserRequestInterface): Promise { + const schemaResponse = await this.appService.createSchema(schemaDetails, user, orgId); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.schema.success.create, @@ -237,26 +153,4 @@ export class SchemaController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } - - /** - * Update an schema alias - * @param updateSchemaDto The details of the schema to be updated - * @returns Success message - */ - @Put('/schema') - @ApiOperation({ summary: 'Update schema', description: 'Update the details of the schema' }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiExcludeEndpoint() - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @UseGuards(AuthGuard('jwt')) - async updateSchema(@Body() updateSchemaDto: UpdateSchemaDto, @Res() res: Response): Promise { - await this.appService.updateSchema(updateSchemaDto); - - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.schema.success.update - }; - return res.status(HttpStatus.OK).json(finalResponse); - } } diff --git a/apps/api-gateway/src/schema/schema.module.ts b/apps/api-gateway/src/schema/schema.module.ts index a27ec6d48..268e35021 100644 --- a/apps/api-gateway/src/schema/schema.module.ts +++ b/apps/api-gateway/src/schema/schema.module.ts @@ -6,7 +6,6 @@ import { SchemaController } from './schema.controller'; import { SchemaService } from './schema.service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -21,6 +20,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [SchemaController], - providers: [SchemaService, NATSClient] + providers: [SchemaService] }) export class SchemaModule { } diff --git a/apps/api-gateway/src/schema/schema.service.ts b/apps/api-gateway/src/schema/schema.service.ts index 16f6b79da..d6476cf29 100644 --- a/apps/api-gateway/src/schema/schema.service.ts +++ b/apps/api-gateway/src/schema/schema.service.ts @@ -1,58 +1,37 @@ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from '../../../../libs/service/base.service'; import { GenericSchemaDTO } from '../dtos/create-schema.dto'; import { ISchemaSearchPayload } from '../interfaces/ISchemaSearch.interface'; import { ISchemaInfo, IUserRequestInterface } from './interfaces'; -import { - ICredDefWithPagination, - ISchemaData, - ISchemasWithPagination -} from '@credebl/common/interfaces/schema.interface'; +import { ICredDefWithPagination, ISchemaData, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; import { GetCredentialDefinitionBySchemaIdDto } from './dtos/get-all-schema.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; - -import { UpdateSchemaResponse } from 'apps/ledger/src/schema/interfaces/schema.interface'; -import { UpdateSchemaDto } from './dtos/update-schema-dto'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class SchemaService extends BaseService { + constructor( - @Inject('NATS_CLIENT') private readonly schemaServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super(`Schema Service`); - } + @Inject('NATS_CLIENT') private readonly schemaServiceProxy: ClientProxy + ) { super(`Schema Service`); } createSchema(schemaDetails: GenericSchemaDTO, user: IUserRequestInterface, orgId: string): Promise { const payload = { schemaDetails, user, orgId }; - return this.natsClient.sendNatsMessage(this.schemaServiceProxy, 'create-schema', payload); + return this.sendNatsMessage(this.schemaServiceProxy, 'create-schema', payload); } + getSchemaById(schemaId: string, orgId: string): Promise { const payload = { schemaId, orgId }; - return this.natsClient.sendNatsMessage(this.schemaServiceProxy, 'get-schema-by-id', payload); + return this.sendNatsMessage(this.schemaServiceProxy, 'get-schema-by-id', payload); } - getSchemas( - schemaSearchCriteria: ISchemaSearchPayload, - user: IUserRequestInterface, - orgId: string - ): Promise { + getSchemas(schemaSearchCriteria: ISchemaSearchPayload, user: IUserRequestInterface, orgId: string): Promise { const schemaSearch = { schemaSearchCriteria, user, orgId }; - return this.natsClient.sendNatsMessage(this.schemaServiceProxy, 'get-schemas', schemaSearch); + return this.sendNatsMessage(this.schemaServiceProxy, 'get-schemas', schemaSearch); } - getcredDefListBySchemaId( - schemaSearchCriteria: GetCredentialDefinitionBySchemaIdDto, - user: IUserRequestInterface - ): Promise { + getcredDefListBySchemaId(schemaSearchCriteria: GetCredentialDefinitionBySchemaIdDto, user: IUserRequestInterface): Promise { const payload = { schemaSearchCriteria, user }; - return this.natsClient.sendNatsMessage(this.schemaServiceProxy, 'get-cred-def-list-by-schemas-id', payload); - } - - updateSchema(schemaDetails: UpdateSchemaDto): Promise { - const payload = { schemaDetails }; - return this.natsClient.sendNatsMessage(this.schemaServiceProxy, 'update-schema', payload); + return this.sendNatsMessage(this.schemaServiceProxy, 'get-cred-def-list-by-schemas-id', payload); } -} +} \ No newline at end of file diff --git a/apps/api-gateway/src/tracer.ts b/apps/api-gateway/src/tracer.ts deleted file mode 100644 index a6ba3a586..000000000 --- a/apps/api-gateway/src/tracer.ts +++ /dev/null @@ -1,69 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck TODO: Facing issues with types, need to fix later -// tracer.ts -import * as dotenv from 'dotenv'; -import * as process from 'process'; - -import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'; -import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api'; - -import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import type { Logger } from '@opentelemetry/api-logs'; -import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; -import { NodeSDK } from '@opentelemetry/sdk-node'; -import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { resourceFromAttributes } from '@opentelemetry/resources'; - -dotenv.config(); - -let otelSDK: NodeSDK | null = null; -let otelLogger: Logger | null = null; -let otelLoggerProviderInstance: LoggerProvider | null = null; -if ('true' === process.env.IS_ENABLE_OTEL) { - diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); - - const resource = resourceFromAttributes({ - [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME, - [SemanticResourceAttributes.SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION, - [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: process.env.HOSTNAME - }); - - const traceExporter = new OTLPTraceExporter({ - url: process.env.OTEL_TRACES_OTLP_ENDPOINT, - headers: { - Authorization: `Api-Key ${process.env.OTEL_HEADERS_KEY}` - } - }); - - const logExporter = new OTLPLogExporter({ - url: process.env.OTEL_LOGS_OTLP_ENDPOINT, - headers: { - Authorization: `Api-Key ${process.env.OTEL_HEADERS_KEY}` - } - }); - - const logProvider = new LoggerProvider({ resource }); - logProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter)); - otelLogger = logProvider.getLogger(process.env.OTEL_LOGGER_NAME); - otelLoggerProviderInstance = logProvider; - - otelSDK = new NodeSDK({ - traceExporter, - resource, - instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation(), new NestInstrumentation()] - }); - - process.on('SIGTERM', () => { - Promise.all([otelSDK!.shutdown(), logProvider.shutdown()]) - // eslint-disable-next-line no-console - .then(() => console.log('SDK and Logger shut down successfully')) - // eslint-disable-next-line no-console - .catch((err) => console.log('Error during shutdown', err)) - .finally(() => process.exit(0)); - }); -} - -export { otelSDK, otelLogger, otelLoggerProviderInstance }; diff --git a/apps/api-gateway/src/user/dto/add-user.dto.ts b/apps/api-gateway/src/user/dto/add-user.dto.ts index 2101d8a48..316e88976 100644 --- a/apps/api-gateway/src/user/dto/add-user.dto.ts +++ b/apps/api-gateway/src/user/dto/add-user.dto.ts @@ -5,7 +5,7 @@ import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, MaxLength, MinLen export class AddUserDetailsDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) @@ -42,6 +42,53 @@ export class AddUserDetailsDto { isHolder?: boolean; } + +export class AddUserDetailsUsernameBasedDto { + + @ApiProperty({ example: '098f6bcd4621d373cade4e832627b4f6' }) + @IsNotEmpty({ message: 'Username is required' }) + @IsString({ message: 'Username should be a string' }) + @Transform(({ value }) => trim(value)) + username: string; + + @ApiProperty({ example: 'Alen' }) + @IsNotEmpty({ message: 'First name is required' }) + @MinLength(2, { message: 'First name must be at least 2 characters' }) + @MaxLength(50, { message: 'First name must be at most 50 characters' }) + @IsString({ message: 'First name should be a string' }) + firstName: string; + + @ApiProperty({ example: 'Harvey' }) + @IsNotEmpty({ message: 'Last name is required' }) + @MinLength(2, { message: 'Last name must be at least 2 characters' }) + @MaxLength(50, { message: 'Last name must be at most 50 characters' }) + @IsString({ message: 'Last name should be a string' }) + lastName: string; + + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Password is required' }) + password: string; + + @ApiProperty({ example: 'false' }) + @IsOptional() + @IsBoolean({ message: 'isPasskey should be boolean' }) + isPasskey?: boolean; + + @ApiPropertyOptional({ example: false }) + @IsOptional() + @IsBoolean({ message: 'isHolder should be boolean' }) + isHolder?: boolean; + + @ApiProperty({ example: 'xxxx-xxxx-xxxx' }) + @IsString({ message: 'clientId should be string' }) + clientId: string; + + @ApiProperty({ example: 'xxxx-xxxxx-xxxxx' }) + @IsString({ message: 'clientSecret should be string' }) + clientSecret: string; +} + export class AddPasskeyDetailsDto { @ApiProperty() @Transform(({ value }) => trim(value)) diff --git a/apps/api-gateway/src/user/dto/create-user.dto.ts b/apps/api-gateway/src/user/dto/create-user.dto.ts index b52f3dc71..2b10c5595 100644 --- a/apps/api-gateway/src/user/dto/create-user.dto.ts +++ b/apps/api-gateway/src/user/dto/create-user.dto.ts @@ -1,41 +1,41 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUrl, MaxLength } from 'class-validator'; import { toLowerCase, trim } from '@credebl/common/cast.helper'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; export class UserEmailVerificationDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) - @Transform(({ value }) => trim(value)) - @Transform(({ value }) => toLowerCase(value)) - @IsNotEmpty({ message: 'Email is required.' }) - @MaxLength(256, { message: 'Email must be at most 256 character.' }) - @IsEmail({}, { message: 'Please provide a valid email' }) - email: string; + @ApiProperty() + @Transform(({ value }) => trim(value)) + @Transform(({ value }) => toLowerCase(value)) + @IsNotEmpty({ message: 'Email is required.' }) + @MaxLength(256, { message: 'Email must be at most 256 character.' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + email: string; - @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) - @Transform(({ value }) => trim(value)) - @IsOptional() - @IsUrl( - { - // eslint-disable-next-line camelcase - require_protocol: true, - // eslint-disable-next-line camelcase + @ApiProperty({ example: 'xxxx-xxxx-xxxx' }) + @IsString({ message: 'clientId should be string' }) + clientId: string; + + @ApiProperty({ example: 'xxxx-xxxxx-xxxxx' }) + @IsString({ message: 'clientSecret should be string' }) + clientSecret: string; + + @ApiPropertyOptional({ example: 'https://example.com/logo.png' }) + @Transform(({ value }) => trim(value)) + @IsOptional() + @IsUrl({ + // eslint-disable-next-line camelcase + require_protocol: true, + // eslint-disable-next-line camelcase require_tld: true }, - { message: 'brandLogoUrl should be a valid URL' } - ) - brandLogoUrl?: string; + { message: 'brandLogoUrl should be a valid URL' }) + brandLogoUrl?: string; @ApiPropertyOptional({ example: 'MyPlatform' }) @Transform(({ value }) => trim(value)) @IsOptional() @IsString({ message: 'platformName should be string' }) platformName?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString({ message: 'clientAlias should be string' }) - @Transform(({ value }) => trim(value)) - clientAlias?: string; } diff --git a/apps/api-gateway/src/user/dto/login-user.dto.ts b/apps/api-gateway/src/user/dto/login-user.dto.ts index bd362ca27..9a9a6c191 100644 --- a/apps/api-gateway/src/user/dto/login-user.dto.ts +++ b/apps/api-gateway/src/user/dto/login-user.dto.ts @@ -1,20 +1,45 @@ -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; export class LoginUserDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) + @ApiProperty({ example: 'awqx@getnada.com' }) @IsEmail({}, { message: 'Please provide a valid email' }) @IsNotEmpty({ message: 'Email is required' }) @IsString({ message: 'Email should be a string' }) @Transform(({ value }) => trim(value)) email: string; + @ValidateIf((obj) => false === obj.isPasskey) @ApiProperty() @Transform(({ value }) => trim(value)) @IsNotEmpty({ message: 'Password is required.' }) - password: string; + password?: string; + @ApiProperty({ example: 'false' }) + @IsOptional() + @IsBoolean({ message: 'isPasskey should be boolean' }) + isPasskey: boolean; +} + + +export class LoginUserNameDto { + @ApiProperty({ example: '098f6bcd4621d373cade4e832627b4f8' }) + @IsNotEmpty({ message: 'username is required' }) + @IsString({ message: 'username should be a string' }) + @Transform(({ value }) => trim(value)) + username: string; + + @ValidateIf((obj) => false === obj.isPasskey) + @ApiProperty() + @Transform(({ value }) => trim(value)) + @IsNotEmpty({ message: 'Password is required.' }) + password?: string; + + @ApiProperty({ example: 'false' }) + @IsOptional() + @IsBoolean({ message: 'isPasskey should be boolean' }) + isPasskey: boolean; } \ No newline at end of file diff --git a/apps/api-gateway/src/user/dto/share-certificate.dto.ts b/apps/api-gateway/src/user/dto/share-certificate.dto.ts new file mode 100644 index 000000000..ca82fd844 --- /dev/null +++ b/apps/api-gateway/src/user/dto/share-certificate.dto.ts @@ -0,0 +1,35 @@ +import { IsArray, IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { trim } from '@credebl/common/cast.helper'; + +interface Attribute { + name: string; + value: string; +} +export class CreateCertificateDto { + + @ApiProperty() + @IsNotEmpty({ message: 'Please provide valid credentialId' }) + @Transform(({ value }) => trim(value)) + @IsString({ message: 'credentialId should be string' }) + credentialId: string; + + @ApiProperty({ example: 'schemaId' }) + @IsNotEmpty({ message: 'Please provide valid schemaId' }) + @Transform(({ value }) => trim(value)) + @IsString({ message: 'schemaId should be string' }) + schemaId: string; + + @ApiProperty({ + example: [ + { + name: 'name', + value: 'value' + } + ] + }) + @IsArray({ message: 'attributes must be a valid array' }) + @IsNotEmpty({ message: 'please provide valid attributes' }) + attributes: Attribute[]; +} diff --git a/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts b/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts index 5a13db9f9..d452e213a 100644 --- a/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts +++ b/apps/api-gateway/src/user/dto/update-platform-settings.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import {IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + export class UpdatePlatformSettingsDto { @ApiProperty({ example: '127.0.0.1' }) @@ -26,4 +27,17 @@ export class UpdatePlatformSettingsDto { @IsOptional() @IsString({ message: 'API endpoint should be string' }) apiEndPoint: string; + + @ApiProperty({ example: 'true' }) + @IsBoolean() + @IsOptional() + @IsNotEmpty({ message: 'enableEcosystem should boolean' }) + enableEcosystem: boolean; + + @ApiProperty({ example: 'true' }) + @IsBoolean() + @IsOptional() + @IsNotEmpty({ message: 'multiEcosystemSupport should boolean' }) + multiEcosystemSupport: boolean; + } \ No newline at end of file diff --git a/apps/api-gateway/src/user/user.controller.ts b/apps/api-gateway/src/user/user.controller.ts index 24d4918d4..5e85bbfbc 100644 --- a/apps/api-gateway/src/user/user.controller.ts +++ b/apps/api-gateway/src/user/user.controller.ts @@ -49,20 +49,20 @@ import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { OrgRoles } from 'libs/org-roles/enums'; import { AwsService } from '@credebl/aws/aws.service'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; +import { CreateCertificateDto } from './dto/share-certificate.dto'; import { UserAccessGuard } from '../authz/guards/user-access-guard'; -import { TrimStringParamPipe } from '@credebl/common/cast.helper'; @UseFilters(CustomExceptionFilter) @Controller('users') @ApiTags('users') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class UserController { constructor( private readonly userService: UserService, private readonly commonService: CommonService, private readonly awsService: AwsService - ) {} + ) { } /** * @@ -105,17 +105,11 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Get public profile details of a user by username. - * - * @param username The username of the user. - * @returns Public profile information. - */ @Get('public-profiles/:username') @ApiExcludeEndpoint() @ApiOperation({ summary: 'Fetch user details', - description: 'Retrieve publicly available details of a user using their username.' + description: 'Fetch user details' }) @ApiParam({ name: 'username', @@ -134,21 +128,16 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Retrieves the profile details of the currently logged-in user. - * - * @returns The user profile details. - */ @Get('/profile') @ApiOperation({ summary: 'Fetch login user details', - description: 'Retrieves the profile details of the currently logged-in user.' + description: 'Fetch login user details' }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() async getProfile(@User() reqUser: user, @Res() res: Response): Promise { const userData = await this.userService.getProfile(reqUser.id); - + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.fetchProfile, @@ -159,14 +148,12 @@ export class UserController { } /** - * Retrieves all platform settings. - * - * @returns The platform settings. + * @returns platform and ecosystem settings */ @Get('/platform-settings') @ApiOperation({ - summary: 'Get all platform settings', - description: 'Retrieves all platform settings.' + summary: 'Get all platform and ecosystem settings', + description: 'Get all platform and ecosystem settings' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @Roles(OrgRoles.PLATFORM_ADMIN) @@ -183,16 +170,10 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Fetch user activities. - * - * @param limit - Number of activities to fetch. - * @returns A response containing user activity data. - */ @Get('/activity') @ApiOperation({ - summary: 'Fetch users activity', - description: 'Fetch a list of recent activities performed by the user.' + summary: 'users activity', + description: 'Fetch users activity' }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() @@ -212,16 +193,14 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } - /** - * Fetch organization invitations. - * - * @returns A paginated list of organization invitations. + * @returns Organization invitation data */ + @Get('/org-invitations') @ApiOperation({ summary: 'organization invitations', - description: 'Retrieve a list of invitations received to the user to join organizations.' + description: 'Fetch organization invitations' }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() @@ -246,7 +225,7 @@ export class UserController { required: false }) async invitations( - @Query() getAllInvitationsDto: GetAllInvitationsDto, + @Query() getAllInvitationsDto: GetAllInvitationsDto, @User() reqUser: user, @Res() res: Response ): Promise { @@ -270,16 +249,12 @@ export class UserController { } /** - * Checks if a user is registered and verifies email existence. * - * @param email The email address to check. - * @returns Returns user registration and email verification status. + * @param email + * @returns User's email exist status */ @Get('/:email') - @ApiOperation({ - summary: 'Check user registration and email verification status', - description: 'Check if a user is already registered and if their email already exists.' - }) + @ApiOperation({ summary: 'Check if user exist', description: 'check user existence' }) async checkUserExist(@Param() emailParam: EmailValidator, @Res() res: Response): Promise { const userDetails = await this.userService.checkUserExist(emailParam.email); @@ -291,33 +266,39 @@ export class UserController { return res.status(HttpStatus.OK).json(finalResponse); } + /** + * @param credentialId + * @returns User credentials + */ + @Get('/user-credentials/:credentialId') + @ApiOperation({ summary: 'Get user credentials by Id', description: 'Get user credentials by Id' }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async getUserCredentialsById(@Param('credentialId') credentialId: string, @Res() res: Response): Promise { + const getUserCrdentialsById = await this.userService.getUserCredentialsById(credentialId); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.userCredentials, + data: getUserCrdentialsById + }; + return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Accept or reject an organization invitation. - * - * @param invitationId The ID of the organization invitation. - * @body AcceptRejectInvitationDto - * @returns The status of the organization invitation response. +* + * @param acceptRejectInvitation + * @returns Organization invitation status */ @Post('/org-invitations/:invitationId') @ApiOperation({ summary: 'accept/reject organization invitation', - description: 'Accept or reject an invitation to join an organization.' + description: 'Accept or Reject organization invitations' }) @UseGuards(AuthGuard('jwt'), UserAccessGuard) @ApiBearerAuth() async acceptRejectInvitaion( - @Body() acceptRejectInvitation: AcceptRejectInvitationDto, - @Param( - 'invitationId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for InvitationId`); - } - }) - ) - invitationId: string, + @Body() acceptRejectInvitation: AcceptRejectInvitationDto, + @Param('invitationId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for InvitationId`); }})) invitationId: string, @User() reqUser: user, @Res() res: Response ): Promise { @@ -330,17 +311,42 @@ export class UserController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + /** + * @Body shareUserCredentials + * @returns User certificate URL + */ + @Post('/certificate') + @ApiOperation({ + summary: 'Share user certificate', + description: 'Share user certificate' + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + async shareUserCertificate( + @Body() shareUserCredentials: CreateCertificateDto, + @Res() res: Response + ): Promise { + const schemaIdParts = shareUserCredentials.schemaId.split(':'); + // eslint-disable-next-line prefer-destructuring + const title = schemaIdParts[2]; + + const imageBuffer = await this.userService.shareUserCertificate(shareUserCredentials); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.user.success.shareUserCertificate || ResponseMessages.user.success.degreeCertificate, + label: title, + data: imageBuffer + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } /** - * Updates the user profile. - * - * @body UpdateUserProfileDto - * @returns A response indicating the success of the update operation. + * @Body updateUserProfileDto + * @returns User details */ @Put('/') @ApiOperation({ summary: 'Update user profile', - description: 'Modify the user profile details such as name, email, or other information.' + description: 'Update user profile' }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) @ApiBearerAuth() @@ -353,52 +359,50 @@ export class UserController { const userId = reqUser.id; updateUserProfileDto.id = userId; await this.userService.updateUserProfile(updateUserProfileDto); - + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.user.success.update }; return res.status(HttpStatus.OK).json(finalResponse); } - - /** - * @body AddPasskeyDetailsDto + /** + * @Body userInfo * @returns User's profile update status */ - - @Put('/password/:email') - @ApiOperation({ - summary: 'Store user password details', - description: 'Securely store and update the user’s password details.' - }) - @ApiExcludeEndpoint() - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), UserAccessGuard) - async addPasskey( - @Body() userInfo: AddPasskeyDetailsDto, - @User() reqUser: user, - @Res() res: Response - ): Promise { - const userDetails = await this.userService.addPasskey(reqUser.email, userInfo); - const finalResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.user.success.update, - data: userDetails - }; - - return res.status(HttpStatus.OK).json(finalResponse); - } + + + @Put('/password/:email') + @ApiOperation({ summary: 'Store user password details', description: 'Store user password details' }) + @ApiExcludeEndpoint() + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), UserAccessGuard) + + async addPasskey( + @Body() userInfo: AddPasskeyDetailsDto, + @User() reqUser: user, + @Res() res: Response + ): Promise { + + const userDetails = await this.userService.addPasskey(reqUser.email, userInfo); + const finalResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.update, + data: userDetails + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } /** - * Updates platform settings. - * @body UpdatePlatformSettingsDto - * - * @returns Status of the update operation. + * @Body platformSettings + * @returns platform and ecosystem settings updated status */ + @Put('/platform-settings') @ApiOperation({ - summary: 'Update platform settings', - description: 'Modify platform settings. Only accessible by platform admins.' + summary: 'Update platform and ecosystem settings', + description: 'Update platform and ecosystem settings' }) @UseGuards(AuthGuard('jwt'), OrgRolesGuard, UserAccessGuard) @Roles(OrgRoles.PLATFORM_ADMIN) diff --git a/apps/api-gateway/src/user/user.module.ts b/apps/api-gateway/src/user/user.module.ts index 02a920ffa..cae3713ae 100644 --- a/apps/api-gateway/src/user/user.module.ts +++ b/apps/api-gateway/src/user/user.module.ts @@ -8,7 +8,6 @@ import { UserService } from './user.service'; import { getNatsOptions } from '@credebl/common/nats.config'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -24,6 +23,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [UserController], - providers: [UserService, CommonService, AwsService, NATSClient] + providers: [UserService, CommonService, AwsService] }) export class UserModule {} diff --git a/apps/api-gateway/src/user/user.service.ts b/apps/api-gateway/src/user/user.service.ts index 03029dd27..490b0678e 100644 --- a/apps/api-gateway/src/user/user.service.ts +++ b/apps/api-gateway/src/user/user.service.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { AcceptRejectInvitationDto } from './dto/accept-reject-invitation.dto'; import { GetAllInvitationsDto } from './dto/get-all-invitations.dto'; @@ -10,86 +11,99 @@ import { IUsersActivity } from 'libs/user-activity/interface'; import { IUserInvitations } from '@credebl/common/interfaces/user.interface'; import { user } from '@prisma/client'; import { PaginationDto } from '@credebl/common/dtos/pagination.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; +import { CreateCertificateDto } from './dto/share-certificate.dto'; @Injectable() export class UserService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('User Service'); } async getProfile(id: string): Promise { const payload = { id }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-profile', payload); } async getPublicProfile(username: string): Promise { - const payload = { username }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-public-profile', payload); + const payload = { username }; + return this.sendNatsMessage(this.serviceProxy, 'get-user-public-profile', payload); + } + + + async getUserCredentialsById(credentialId: string): Promise { + const payload = { credentialId }; + return this.sendNatsMessage(this.serviceProxy, 'get-user-credentials-by-id', payload); } async updateUserProfile(updateUserProfileDto: UpdateUserProfileDto): Promise { const payload = { updateUserProfileDto }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-user-profile', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-user-profile', payload); } async findUserinSupabase(id: string): Promise { const payload = { id }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-by-supabase', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-by-supabase', payload); } async findUserinKeycloak(id: string): Promise { const payload = { id }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-by-keycloak', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-by-keycloak', payload); } - + async invitations(id: string, status: string, getAllInvitationsDto: GetAllInvitationsDto): Promise { const { pageNumber, pageSize, search } = getAllInvitationsDto; const payload = { id, status, pageNumber, pageSize, search }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-org-invitations', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-org-invitations', payload); } - async acceptRejectInvitaion(acceptRejectInvitation: AcceptRejectInvitationDto, userId: string): Promise { + async acceptRejectInvitaion( + acceptRejectInvitation: AcceptRejectInvitationDto, + userId: string + ): Promise { const payload = { acceptRejectInvitation, userId }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'accept-reject-invitations', payload); + return this.sendNatsMessage(this.serviceProxy, 'accept-reject-invitations', payload); + } + + async shareUserCertificate( + shareUserCredentials: CreateCertificateDto + ): Promise { + return this.sendNatsMessage(this.serviceProxy, 'share-user-certificate', shareUserCredentials); } - async get(paginationDto: PaginationDto): Promise { + async get( + paginationDto:PaginationDto + ): Promise { const { pageNumber, pageSize, search } = paginationDto; const payload = { pageNumber, pageSize, search }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-users', payload); + return this.sendNatsMessage(this.serviceProxy, 'fetch-users', payload); } async checkUserExist(userEmail: string): Promise { const payload = { userEmail }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'check-user-exist', payload); + return this.sendNatsMessage(this.serviceProxy, 'check-user-exist', payload); } async getUserActivities(userId: string, limit: number): Promise { const payload = { userId, limit }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-activity', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-activity', payload); } async addPasskey(userEmail: string, userInfo: AddPasskeyDetailsDto): Promise { const payload = { userEmail, userInfo }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'add-passkey', payload); + return this.sendNatsMessage(this.serviceProxy, 'add-passkey', payload); } async updatePlatformSettings(platformSettings: UpdatePlatformSettingsDto): Promise { const payload = { platformSettings }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'update-platform-settings', payload); + return this.sendNatsMessage(this.serviceProxy, 'update-platform-settings', payload); } async getPlatformSettings(): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'fetch-platform-settings', ''); + return this.sendNatsMessage(this.serviceProxy, 'fetch-platform-settings', ''); } async getUserByUserIdInKeycloak(email: string): Promise { const payload = { email }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'get-user-info-by-user-email-keycloak', payload); + return this.sendNatsMessage(this.serviceProxy, 'get-user-info-by-user-email-keycloak', payload); } } diff --git a/apps/api-gateway/src/user/utils/index.ts b/apps/api-gateway/src/user/utils/index.ts deleted file mode 100644 index 68193f49d..000000000 --- a/apps/api-gateway/src/user/utils/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { IClientDetailsSSO } from '@credebl/common/interfaces/user.interface'; -import { encryptClientCredential } from '@credebl/common/cast.helper'; - -export const getDefaultClient = async (): Promise => ({ - alias: process.env.PLATFORM_NAME?.toUpperCase(), - domain: process.env.FRONT_END_URL, - clientId: await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID), - clientSecret: await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET) -}); - -// Now getting from env, but can get from DB -function getClientDetails(alias: string): IClientDetailsSSO { - const clientIdKey = `${alias}_KEYCLOAK_MANAGEMENT_CLIENT_ID`; - const clientSecretKey = `${alias}_KEYCLOAK_MANAGEMENT_CLIENT_SECRET`; - const domainKey = `${alias}_DOMAIN`; - const aliasNameKey = `${alias}_ALIAS`; - - const clientId = process.env[clientIdKey]; - const clientSecret = process.env[clientSecretKey]; - const domain = process.env[domainKey]; - const aliasName = process.env[aliasNameKey] || alias; - - const clientDetails: IClientDetailsSSO = { - clientId, - clientSecret, - domain, - alias: aliasName - }; - - return clientDetails; -} - -export async function getCredentialsByAlias(alias: string): Promise { - const defaultClient = await getDefaultClient(); - if (alias.toUpperCase() === defaultClient.alias) { - return defaultClient; - } - - const clientDetails = getClientDetails(alias); - - if (!clientDetails.clientId || !clientDetails.clientSecret || !clientDetails.domain) { - throw new Error(`Missing configuration for SSO client: ${alias}`); - } - - return clientDetails; -} diff --git a/apps/api-gateway/src/utilities/utilities.controller.ts b/apps/api-gateway/src/utilities/utilities.controller.ts index 7c8b226f4..92463a9a9 100644 --- a/apps/api-gateway/src/utilities/utilities.controller.ts +++ b/apps/api-gateway/src/utilities/utilities.controller.ts @@ -1,13 +1,6 @@ -import { - ApiBearerAuth, - ApiForbiddenResponse, - ApiOperation, - ApiResponse, - ApiTags, - ApiUnauthorizedResponse -} from '@nestjs/swagger'; -import { Controller, UseFilters, Post, Body, Res, HttpStatus, Param, UseGuards, ParseBoolPipe } from '@nestjs/common'; -import IResponse from '@credebl/common/interfaces/response.interface'; +import { ApiBearerAuth, ApiForbiddenResponse, ApiOperation, ApiResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; +import { Controller, UseFilters, Post, Body, Res, HttpStatus, Param, UseGuards } from '@nestjs/common'; +import IResponse from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; @@ -21,20 +14,18 @@ import { AuthGuard } from '@nestjs/passport'; @UseFilters(CustomExceptionFilter) @Controller('utilities') @ApiTags('utilities') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) export class UtilitiesController { - constructor(private readonly utilitiesService: UtilitiesService) {} - /** - * Create a shortening URL - * @param shorteningUrlDto The details for the URL to be shortened - * @param res The response object - * @returns The created shortening URL details - */ + constructor( + private readonly utilitiesService: UtilitiesService + ) { } + + @Post('/') - @ApiOperation({ summary: 'Create Shortening URL', description: 'Create a shortening URL for the provided details.' }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Shortening URL created successfully', type: ApiResponseDto }) + @ApiOperation({ summary: 'Create a shorteningurl', description: 'Create a shortening url' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) async createShorteningUrl(@Body() shorteningUrlDto: UtilitiesDto, @Res() res: Response): Promise { const shorteningUrl = await this.utilitiesService.createShorteningUrl(shorteningUrlDto); const finalResponse: IResponse = { @@ -45,33 +36,13 @@ export class UtilitiesController { return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Store an object and return a short URL to it - * @param storeObjectDto The object details to be stored - * @param persist Whether the object should be persisted - * @param res The response object - * @returns The created short URL representing the object - */ @Post('/store-object/:persist') @ApiBearerAuth() @UseGuards(AuthGuard('jwt')) - @ApiOperation({ - summary: 'Store Object and Create Short URL', - description: 'Store an object and create a short URL representing the object.' - }) - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Object stored and short URL created successfully', - type: ApiResponseDto - }) - async storeObject( - @Body() storeObjectDto: StoreObjectDto, - // Since Params are always strings, we need to parse the value. - // This prevent for example 'false' being considered as a truthy value - @Param('persist', ParseBoolPipe) persist: boolean, - @Res() res: Response - ): Promise { - const shorteningUrl = await this.utilitiesService.storeObject(persist, storeObjectDto); + @ApiOperation({ summary: 'Store an object and return a short url to it', description: 'Create a short url representing the object' }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + async storeObject(@Body() storeObjectDto: StoreObjectDto, @Param('persist') persist: boolean, @Res() res: Response): Promise { + const shorteningUrl = await this.utilitiesService.storeObject(persist.valueOf(), storeObjectDto); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, message: ResponseMessages.storeObject.success.storeObject, @@ -79,4 +50,6 @@ export class UtilitiesController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + } + diff --git a/apps/api-gateway/src/utilities/utilities.module.ts b/apps/api-gateway/src/utilities/utilities.module.ts index 3fc905a4f..7dda4be54 100644 --- a/apps/api-gateway/src/utilities/utilities.module.ts +++ b/apps/api-gateway/src/utilities/utilities.module.ts @@ -5,10 +5,10 @@ import { ConfigModule } from '@nestjs/config'; import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { ImageServiceService } from '@credebl/image-service'; import { UtilitiesController } from './utilities.controller'; import { UtilitiesService } from './utilities.service'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -19,11 +19,13 @@ import { NATSClient } from '@credebl/common/NATSClient'; name: 'NATS_CLIENT', transport: Transport.NATS, options: getNatsOptions(CommonConstants.UTILITY_SERVICE, process.env.API_GATEWAY_NKEY_SEED) + }, CommonModule ]) ], controllers: [UtilitiesController], - providers: [UtilitiesService, CommonService, NATSClient] + providers: [UtilitiesService, CommonService, ImageServiceService] }) -export class UtilitiesModule {} +export class UtilitiesModule { } + diff --git a/apps/api-gateway/src/utilities/utilities.service.ts b/apps/api-gateway/src/utilities/utilities.service.ts index 58f3f5cd7..6e7eb54fb 100644 --- a/apps/api-gateway/src/utilities/utilities.service.ts +++ b/apps/api-gateway/src/utilities/utilities.service.ts @@ -1,25 +1,21 @@ import { Inject, Injectable } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { StoreObjectDto, UtilitiesDto } from './dtos/shortening-url.dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class UtilitiesService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy) { super('OrganizationService'); } async createShorteningUrl(shorteningUrlDto: UtilitiesDto): Promise { - return this.natsClient.sendNatsMessage(this.serviceProxy, 'create-shortening-url', shorteningUrlDto); + return this.sendNatsMessage(this.serviceProxy, 'create-shortening-url', shorteningUrlDto); } async storeObject(persistent: boolean, storeObjectDto: StoreObjectDto): Promise { const storeObj = storeObjectDto.data; - const payload = { persistent, storeObj }; - return this.natsClient.sendNatsMessage(this.serviceProxy, 'store-object-return-url', payload); + const payload = {persistent, storeObj}; + return this.sendNatsMessage(this.serviceProxy, 'store-object-return-url', payload); } } diff --git a/apps/api-gateway/src/verification/dto/request-proof.dto.ts b/apps/api-gateway/src/verification/dto/request-proof.dto.ts index 6f1b41072..10f1f8c5d 100644 --- a/apps/api-gateway/src/verification/dto/request-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/request-proof.dto.ts @@ -1,4 +1,4 @@ -import { ArrayNotEmpty, IsArray, IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsObject, IsOptional, IsString, ValidateIf, ValidateNested, IsUUID, ArrayUnique, ArrayMaxSize, ArrayMinSize } from 'class-validator'; +import { ArrayNotEmpty, IsArray, IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumberString, IsObject, IsOptional, IsString, ValidateIf, ValidateNested, IsUUID, ArrayUnique, ArrayMaxSize } from 'class-validator'; import { trim } from '@credebl/common/cast.helper'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Transform, Type } from 'class-transformer'; @@ -14,7 +14,7 @@ export class ProofRequestAttribute { attributeName?: string; @ValidateIf((obj) => obj.attributeName === undefined) - @IsArray({ message: 'attributeNames must be an array' }) + @IsArray({ message: 'attributeNames must be an array.' }) @ArrayNotEmpty({ message: 'array can not be empty' }) @IsString({ each: true }) @IsNotEmpty({ each: true, message: 'each element cannot be empty' }) @@ -23,25 +23,23 @@ export class ProofRequestAttribute { @ApiPropertyOptional() @IsString() @IsOptional() - @IsNotEmpty({ message: 'schemaId is required' }) schemaId?: string; @ApiPropertyOptional() - @ValidateIf((obj) => obj.value !== undefined || obj.condition !== undefined) - @IsNotEmpty({ message: 'condition is required' }) - @IsString({ message: 'condition must be a string' }) + @IsString() + @IsOptional() + @IsNotEmpty({ message: 'condition is required.' }) condition?: string; - + @ApiPropertyOptional() - @ValidateIf((obj) => obj.condition !== undefined || obj.value !== undefined) - @IsNotEmpty({ message: 'value is required' }) - @IsNumberString({}, { message: 'value must be a number' }) + @IsOptional() + @IsNotEmpty({ message: 'value is required.' }) + @IsNumberString({}, { message: 'Value must be a number' }) value?: string; @ApiPropertyOptional() @IsString() @IsOptional() - @IsNotEmpty({ message: 'credDefId is required' }) credDefId?: string; } @@ -56,7 +54,7 @@ class ProofPayload { @IsString({ message: 'parentThreadId must be in string' }) @IsNotEmpty({ message: 'please provide valid parentThreadId' }) @IsOptional() - parentThreadId?: string; + parentThreadId: string; @ApiPropertyOptional() @IsBoolean({ message: 'willConfirm must be in boolean' }) @@ -71,17 +69,35 @@ class ProofPayload { protocolVersion: string; } +export class Filter { + @ApiProperty() + @IsString({ message: 'type must be in string' }) + @IsNotEmpty({ message: 'type is required.' }) + type: string; + + @ApiProperty() + @IsString({ message: 'pattern must be in string' }) + @IsNotEmpty({ message: 'pattern is required.' }) + pattern: string; +} export class Fields { @ApiProperty() @IsArray() - @IsNotEmpty({ message: 'path is required' }) + @IsNotEmpty({ message: 'path is required.' }) path: string[]; + + @ApiProperty() + @IsOptional() + @IsNotEmpty({ message: 'filter is required.' }) + @ValidateNested() + @Type(() => Filter) + filter: Filter; } export class Constraints { @ApiProperty({type: () => [Fields]}) @IsOptional() - @IsNotEmpty({ message: 'Fields are required' }) + @IsNotEmpty({ message: 'Fields are required.' }) @ValidateNested() @Type(() => Fields) fields: Fields[]; @@ -90,31 +106,32 @@ export class Constraints { export class Schema { @ApiProperty() - @IsNotEmpty({ message: 'uri is required' }) + @IsNotEmpty({ message: 'uri is required.' }) @IsString() uri:string; } export class InputDescriptors { @ApiProperty() - @IsNotEmpty({ message: 'id is required' }) + @IsNotEmpty({ message: 'id is required.' }) @IsString() id:string; @ApiProperty() @IsString() @IsOptional() - @IsNotEmpty({ message: 'name is required' }) + @IsNotEmpty({ message: 'name is required.' }) name:string; @ApiProperty() @IsString() @IsOptional() - @IsNotEmpty({ message: 'purpose is required' }) + @IsNotEmpty({ message: 'purpose is required.' }) purpose:string; - + + @ApiProperty({type: () => [Schema]}) - @IsNotEmpty({ message: 'schema is required' }) + @IsNotEmpty({ message: 'schema is required.' }) @ValidateNested() @Type(() => Schema) schema:Schema[]; @@ -122,7 +139,7 @@ export class InputDescriptors { @ApiProperty({type: () => Constraints}) @IsOptional() - @IsNotEmpty({ message: 'Constraints are required' }) + @IsNotEmpty({ message: 'Constraints are required.' }) @ValidateNested() @Type(() => Constraints) constraints:Constraints; @@ -131,26 +148,24 @@ export class InputDescriptors { export class ProofRequestPresentationDefinition { - @ApiProperty() @IsString() - @IsNotEmpty({ message: 'id is required' }) + @IsNotEmpty({ message: 'id is required.' }) id: string; @IsString() @IsOptional() name: string; - @ApiPropertyOptional() @IsString() @IsOptional() - purpose?: string; + purpose: string; - @ApiProperty({type: () => [InputDescriptors]}) + @ApiProperty({type: () => [InputDescriptors]}) + @IsNotEmpty({ message: 'inputDescriptors is required.' }) @IsArray({ message: 'inputDescriptors must be an array' }) - @IsNotEmpty({ message: 'inputDescriptors is required' }) - @ArrayMinSize(1) - @ValidateNested({each:true}) + @IsObject({ each: true }) @Type(() => InputDescriptors) + @ValidateNested() // eslint-disable-next-line camelcase input_descriptors:InputDescriptors[]; } @@ -169,7 +184,7 @@ export class ProofRequestAttributeDto { type: () => [ProofRequestAttribute] }) @IsArray({ message: 'attributes must be in array' }) -@ValidateNested({ each: true }) +@ValidateNested() @IsObject({ each: true }) @IsNotEmpty({ message: 'please provide valid attributes' }) @Type(() => ProofRequestAttribute) @@ -198,7 +213,13 @@ export class IndyDto { indy: ProofRequestAttributeDto; } -export class RequestProofDtoBase extends ProofPayload { +export class RequestProofDto extends ProofPayload { + @ApiProperty() + @IsString() + @Transform(({ value }) => trim(value)) + @IsUUID() + @IsNotEmpty({ message: 'connectionId is required.' }) + connectionId: string; @ApiProperty({ 'example': @@ -214,7 +235,7 @@ export class RequestProofDtoBase extends ProofPayload { } ] } - }, + }, type: () => [IndyDto] }) @IsOptional() @@ -223,14 +244,12 @@ export class RequestProofDtoBase extends ProofPayload { @IsNotEmpty({ message: 'ProofFormatDto must not be empty' }) @Type(() => IndyDto) proofFormats?: IndyDto; - + @ApiProperty({ 'example': { - id: '32f54163-7166-48f1-93d8-ff217bdb0653', - purpose: 'Used for KYC verification.', - // eslint-disable-next-line camelcase - input_descriptors: [ + id: '32f54163-7166-48f1-93d8-ff217bdb0653', + inputDescriptors: [ { 'id': 'healthcare_input_1', 'name': 'Medical History', @@ -243,7 +262,7 @@ export class RequestProofDtoBase extends ProofPayload { 'constraints': { 'fields': [ { - 'path': ['$.credentialSubject.PatientID'] + 'path': ['$.PatientID'] } ] } @@ -263,44 +282,20 @@ export class RequestProofDtoBase extends ProofPayload { @IsOptional() @IsString({ message: 'comment must be in string' }) comment: string; - + type:ProofRequestType; - + orgId: string; - - @ApiPropertyOptional({enum:AutoAccept}) + + @ApiPropertyOptional() @IsString({ message: 'auto accept proof must be in string' }) @IsNotEmpty({ message: 'please provide valid auto accept proof' }) @IsOptional() @IsEnum(AutoAccept, { - message: `Invalid auto accept proof. It should be one of: ${Object.values(AutoAccept).join(', ')}` + message: `Invalid auto accept proof. It should be one of: ${Object.values(AutoAccept).join(', ')}` }) autoAcceptProof: AutoAccept; - version: string; } -export class RequestProofDtoV1 extends RequestProofDtoBase { - @ApiProperty({ - example: '32f54163-7166-48f1-93d8-ff217bdb0653' - }) - @IsNotEmpty({ message: 'connectionId is required' }) - @IsString({ message: 'connectionId must be a string' }) - @IsUUID() - connectionId:string; -} - - -export class RequestProofDtoV2 extends RequestProofDtoBase { - @ApiProperty({ - example: ['32f54163-7166-48f1-93d8-ff217bdb0653'] - }) - @IsNotEmpty({ each: true, message: 'connectionId array elements must not be empty' }) - @IsArray({ message: 'connectionId must be an array' }) - @ArrayMinSize(1, { message: 'connectionId must contain at least 1 element' }) - @ArrayMaxSize(Number(process.env.PROOF_REQ_CONN_LIMIT), { message: `Limit reached (${process.env.PROOF_REQ_CONN_LIMIT} connections max).` }) - @IsUUID('all', { each: true, message: 'Each connectionId must be a valid UUID' }) - connectionId: string[]; -} - export class OutOfBandRequestProof extends ProofPayload { @ApiProperty({ @@ -394,7 +389,6 @@ export class SendProofRequestPayload { 'example': { id: '32f54163-7166-48f1-93d8-ff217bdb0653', - purpose: 'Used for KYC verification.', inputDescriptors: [ { 'id': 'banking_input_1', @@ -408,7 +402,11 @@ export class SendProofRequestPayload { 'constraints': { 'fields': [ { - 'path': ['$.issuer'] + 'path': ['$.issuer.id'], + 'filter': { + 'type': 'string', + 'pattern': 'did:example:123|did:example:456' + } } ] } @@ -440,18 +438,16 @@ export class SendProofRequestPayload { @IsString({ message: 'label must be in string' }) label: string; - // TODO: [Credo-ts] Issue with parentThreadId in creating an OOB proof request. - // This causes failures in OOB connection establishment. - // @ApiPropertyOptional() - // @IsOptional() - // @IsUUID() - // @IsNotEmpty({ message: 'please provide valid parentThreadId' }) - // parentThreadId: string; + @ApiPropertyOptional() + @IsOptional() + @IsUUID() + @IsNotEmpty({ message: 'please provide valid parentThreadId' }) + parentThreadId: string; @ApiProperty({ example: true }) @IsBoolean() @IsOptional() - @IsNotEmpty({message:'Please provide the flag for shorten url'}) + @IsNotEmpty({message:'Please provide the flag for shorten url.'}) isShortenUrl?: boolean; @ApiPropertyOptional() diff --git a/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts b/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts index d4f150c48..bd9bf9b2e 100644 --- a/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts +++ b/apps/api-gateway/src/verification/dto/webhook-proof.dto.ts @@ -1,5 +1,5 @@ -import { ApiPropertyOptional } from "@nestjs/swagger"; -import { IsOptional } from "class-validator"; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; interface IWebhookPresentationProof { threadId: string; @@ -44,7 +44,7 @@ export class WebhookPresentationProofDto { @ApiPropertyOptional() @IsOptional() parentThreadId?: string; - + @ApiPropertyOptional() @IsOptional() presentationId: string; @@ -76,8 +76,4 @@ export class WebhookPresentationProofDto { @ApiPropertyOptional() @IsOptional() orgId: string; - - @ApiPropertyOptional() - @IsOptional() - errorMessage: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/enum/verification.enum.ts b/apps/api-gateway/src/verification/enum/verification.enum.ts index 144f94948..87c8b88a2 100644 --- a/apps/api-gateway/src/verification/enum/verification.enum.ts +++ b/apps/api-gateway/src/verification/enum/verification.enum.ts @@ -8,27 +8,4 @@ export enum SortFields { export enum ProofRequestType { INDY = 'indy', PRESENTATIONEXCHANGE = 'presentationExchange' -} - -export enum VerificationMethodLabel { - REQUEST_PROOF = 'request-proof' -} - -export enum ProtocolVersionType { - PROTOCOL_VERSION_1 = 'v1', - PROTOCOL_VERSION_2 = 'v2' - -} - -export enum ProofRequestLabel { - PROOF_REQUEST = 'Proof Request' -} - -export enum ProofVersion { - PROOF_VERSION = '1.0' -} - -export enum API_Version { - VERSION_1 = 'v1', - version_neutral = 'version_neutral' } \ No newline at end of file diff --git a/apps/api-gateway/src/verification/interfaces/verification.interface.ts b/apps/api-gateway/src/verification/interfaces/verification.interface.ts index cac18fa67..341c844db 100644 --- a/apps/api-gateway/src/verification/interfaces/verification.interface.ts +++ b/apps/api-gateway/src/verification/interfaces/verification.interface.ts @@ -1,4 +1,4 @@ -import { IUserRequestInterface } from "../../interfaces/IUserRequestInterface"; +import { IUserRequestInterface } from '../../interfaces/IUserRequestInterface'; export interface IProofRequestAttribute { attributeName: string; diff --git a/apps/api-gateway/src/verification/verification.controller.ts b/apps/api-gateway/src/verification/verification.controller.ts index cbd952442..2a143e0a5 100644 --- a/apps/api-gateway/src/verification/verification.controller.ts +++ b/apps/api-gateway/src/verification/verification.controller.ts @@ -2,37 +2,21 @@ /* eslint-disable no-param-reassign */ /* eslint-disable camelcase */ import { - ApiBearerAuth, - ApiTags, - ApiOperation, - ApiResponse, - ApiUnauthorizedResponse, - ApiForbiddenResponse, - ApiBody, - ApiQuery, - ApiExcludeEndpoint + ApiBearerAuth, + ApiTags, + ApiOperation, + ApiResponse, + ApiUnauthorizedResponse, + ApiForbiddenResponse, + ApiBody, + ApiQuery, + ApiExcludeEndpoint } from '@nestjs/swagger'; -import { - Controller, - Logger, - Post, - Body, - Get, - Query, - HttpStatus, - Res, - UseGuards, - Param, - UseFilters, - BadRequestException, - ParseUUIDPipe, - Delete, - Version -} from '@nestjs/common'; +import { Controller, Logger, Post, Body, Get, Query, HttpStatus, Res, UseGuards, Param, UseFilters, BadRequestException, ParseUUIDPipe, Delete } from '@nestjs/common'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; -import { SendProofRequestPayload, RequestProofDtoV1, RequestProofDtoV2 } from './dto/request-proof.dto'; +import { SendProofRequestPayload, RequestProofDto } from './dto/request-proof.dto'; import { VerificationService } from './verification.service'; import IResponseType, { IResponse } from '@credebl/common/interfaces/response.interface'; import { Response } from 'express'; @@ -44,400 +28,284 @@ import { AuthGuard } from '@nestjs/passport'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; import { WebhookPresentationProofDto } from './dto/webhook-proof.dto'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; +import { ImageServiceService } from '@credebl/image-service'; import { User } from '../authz/decorators/user.decorator'; import { GetAllProofRequestsDto } from './dto/get-all-proof-requests.dto'; import { IProofRequestSearchCriteria } from './interfaces/verification.interface'; -import { API_Version, ProofRequestType, SortFields } from './enum/verification.enum'; +import { ProofRequestType, SortFields } from './enum/verification.enum'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { user } from '@prisma/client'; -import { TrimStringParamPipe } from '@credebl/common/cast.helper'; -import { Validator } from '@credebl/common/validator'; @UseFilters(CustomExceptionFilter) @Controller() @ApiTags('verifications') export class VerificationController { - constructor(private readonly verificationService: VerificationService) {} + constructor( + private readonly verificationService: VerificationService, + private readonly imageServiceService: ImageServiceService + ) { } - private readonly logger = new Logger('VerificationController'); + private readonly logger = new Logger('VerificationController'); - /** - * Get verified proof details - * @param proofId The ID of the proof - * @param orgId The ID of the organization - * @returns Verified proof details - */ - @Get('/orgs/:orgId/verified-proofs/:proofId') - @ApiOperation({ - summary: 'Get verified proof details', - description: 'Retrieve the details of a verified proof for a specific organization.' - }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - async getVerifiedProofDetails( - @Res() res: Response, - @User() user: IUserRequest, - @Param('proofId') proofId: string, - @Param('orgId') orgId: string - ): Promise { - const sendProofRequest = await this.verificationService.getVerifiedProofDetails(proofId, orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.verification.success.verifiedProofDetails, - data: sendProofRequest - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * + * @param proofId + * @param orgId + * @returns Verified proof details + */ + @Get('/orgs/:orgId/verified-proofs/:proofId') + @ApiOperation({ + summary: `Get verified proof details`, + description: `Get verified proof details` + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + async getVerifiedProofDetails( + @Res() res: Response, + @User() user: IUserRequest, + @Param('proofId') proofId: string, + @Param('orgId') orgId: string + ): Promise { + const sendProofRequest = await this.verificationService.getVerifiedProofDetails(proofId, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.verifiedProofDetails, + data: sendProofRequest + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get proof presentation details by proofId - * @param proofId The ID of the proof - * @param orgId The ID of the organization - * @returns Proof presentation details by proofId - */ - @Get('/orgs/:orgId/proofs/:proofId') - @ApiOperation({ - summary: 'Get proof presentation by proof Id', - description: 'Retrieve the details of a proof presentation by its proof ID for a specific organization.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @ApiBearerAuth() - async getProofPresentationById( - @Res() res: Response, - @User() user: IUserRequest, - @Param( - 'proofId', - TrimStringParamPipe, - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.verification.error.invalidProofId); - } - }) - ) - proofId: string, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string - ): Promise { - const getProofPresentationById = await this.verificationService.getProofPresentationById(proofId, orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.verification.success.fetch, - data: getProofPresentationById - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + /** + * Get proof presentation details by proofId + * @param proofId + * @param orgId + * @returns Proof presentation details by proofId + */ + @Get('/orgs/:orgId/proofs/:proofId') + @ApiOperation({ + summary: `Get proof presentation by proof Id`, + description: `Get proof presentation by proof Id` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @ApiBearerAuth() + async getProofPresentationById( + @Res() res: Response, + @User() user: IUserRequest, + @Param('proofId') proofId: string, + @Param('orgId') orgId: string + ): Promise { + const getProofPresentationById = await this.verificationService.getProofPresentationById(proofId, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.fetch, + data: getProofPresentationById + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get proof presentation details by issuerId - * @param proofId The ID of the proof - * @param issuerId The ID of the issuer - * @returns Proof presentation details by issuerId - */ - @Get('/orgs/proofs') - @ApiOperation({ - summary: 'Get verified proof presentation details by issuer Id', - description: 'Retrieve the details of a proof presentation by its issuer Id' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @UseGuards(AuthGuard('jwt')) - @ApiBearerAuth() - async getProofPresentationByIssuerId( - @Res() res: Response, - @User() user: IUserRequest, - @Query('issuerId') issuerId: string - ): Promise { - const verifiedProofDetails = await this.verificationService.getPresentationDetailsByIssuerId(issuerId, user); + /** + * Get all proof presentations + * @param user + * @param orgId + * @returns All proof presentations details + */ + @Get('/orgs/:orgId/proofs') + @ApiOperation({ + summary: `Get all proof presentations by orgId`, + description: `Get all proof presentations by orgId` + }) + @ApiQuery({ + name: 'sortField', + enum: SortFields, + required: false + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBearerAuth() + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async getProofPresentations( + @Query() getAllProofRequests: GetAllProofRequestsDto, + @Res() res: Response, + @User() user: IUserRequest, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string + ): Promise { + const { pageSize, search, pageNumber, sortField, sortBy } = getAllProofRequests; + const proofRequestsSearchCriteria: IProofRequestSearchCriteria = { + pageNumber, + search, + pageSize, + sortField, + sortBy + }; - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.verification.success.fetch, - data: verifiedProofDetails - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + const proofPresentationDetails = await this.verificationService.getProofPresentations(proofRequestsSearchCriteria, user, orgId); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.fetch, + data: proofPresentationDetails + }; + return res.status(HttpStatus.OK).json(finalResponse); + } - /** - * Get all proof presentations - * @param user The user making the request - * @param orgId The ID of the organization - * @returns All proof presentations details - */ - @Get('/orgs/:orgId/proofs') - @ApiOperation({ - summary: 'Get all proof presentations by orgId', - description: 'Retrieve all proof presentations for a the organization. Supports pagination and sorting.' - }) - @ApiQuery({ - name: 'sortField', - enum: SortFields, - required: false - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER, OrgRoles.HOLDER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async getProofPresentations( - @Query() getAllProofRequests: GetAllProofRequestsDto, - @Res() res: Response, - @User() user: IUserRequest, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); - } + /** + * Send proof request + * @param orgId + * @returns Requested proof presentation details + */ + @Post('/orgs/:orgId/proofs') + @ApiOperation({ + summary: `Sends a proof request`, + description: `Sends a proof request` + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBody({ type: RequestProofDto })@ApiQuery({ + name: 'requestType', + enum: ProofRequestType }) - ) - orgId: string - ): Promise { - const { pageSize, search, pageNumber, sortField, sortBy } = getAllProofRequests; - const proofRequestsSearchCriteria: IProofRequestSearchCriteria = { - pageNumber, - search, - pageSize, - sortField, - sortBy - }; - - const proofPresentationDetails = await this.verificationService.getProofPresentations( - proofRequestsSearchCriteria, - user, - orgId - ); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.verification.success.fetch, - data: proofPresentationDetails - }; - return res.status(HttpStatus.OK).json(finalResponse); - } + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + async sendPresentationRequest( + @Res() res: Response, + @User() user: IUserRequest, + @Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(`Invalid format for orgId`); }})) orgId: string, + @Body() requestProof: RequestProofDto, + @Query('requestType') requestType:ProofRequestType = ProofRequestType.INDY + ): Promise { - /** - * Send proof request - * @param orgId The ID of the organization - * @returns Requested proof presentation details - */ - @Post('/orgs/:orgId/proofs') - @ApiOperation({ - summary: 'Sends a proof request', - description: 'Send a proof request to a specific organization.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBody({ type: RequestProofDtoV1 }) - @ApiQuery({ - name: 'requestType', - enum: ProofRequestType - }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - async sendPresentationRequest( - @Res() res: Response, - @User() user: IUserRequest, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); + if (requestType === ProofRequestType.INDY) { + if (!requestProof.proofFormats) { + throw new BadRequestException(`type: ${requestType} requires proofFormats`); + } } - }) - ) - orgId: string, - @Body() requestProof: RequestProofDtoV1, - @Query('requestType') requestType: ProofRequestType = ProofRequestType.INDY - ): Promise { - if (requestType === ProofRequestType.INDY && !requestProof.proofFormats) { - throw new BadRequestException(`type: ${requestType} requires proofFormats`); - } - - if (requestType === ProofRequestType.PRESENTATIONEXCHANGE && !requestProof.presentationDefinition) { - throw new BadRequestException(`type: ${requestType} requires presentationDefinition`); - } - if (requestType === ProofRequestType.INDY) { - Validator.validateIndyProofAttributes(requestProof.proofFormats.indy.attributes); - } - const version = API_Version.version_neutral; - requestProof.version = version; - requestProof.orgId = orgId; - requestProof.type = requestType; - const proofData = await this.verificationService.sendProofRequest(requestProof, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.send, - data: proofData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + if (requestType === ProofRequestType.PRESENTATIONEXCHANGE) { + if (!requestProof.presentationDefinition) { + throw new BadRequestException(`type: ${requestType} requires presentationDefinition`); + } + } + if (requestProof.proofFormats) { + const attributeArray = []; + for (const attrData of requestProof.proofFormats.indy.attributes) { + if (0 === attributeArray.length) { + attributeArray.push(Object.values(attrData)[0]); + } else if (!attributeArray.includes(Object.values(attrData)[0])) { + attributeArray.push(Object.values(attrData)[0]); + } else { + throw new BadRequestException('Please provide unique attribute names'); + } - /** - * Send proof request v1 - * @param orgId The ID of the organization - * @returns Requested proof presentation details - */ - @Version('2') - @Post('/orgs/:orgId/proofs') - @ApiOperation({ - summary: 'Sends a proof request', - description: 'Send a proof request on multiple connections for a the organization.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBody({ type: RequestProofDtoV2 }) - @ApiQuery({ - name: 'requestType', - enum: ProofRequestType - }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - async sendPresentationRequestV1( - @Res() res: Response, - @User() user: IUserRequest, - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(`Invalid format for orgId`); } - }) - ) - orgId: string, - @Body() requestProof: RequestProofDtoV2, - @Query('requestType') requestTypeV1: ProofRequestType = ProofRequestType.INDY - ): Promise { - if (requestTypeV1 === ProofRequestType.INDY && !requestProof.proofFormats) { - throw new BadRequestException(`type: ${requestTypeV1} requires proofFormats`); - } + } - if (requestTypeV1 === ProofRequestType.PRESENTATIONEXCHANGE && !requestProof.presentationDefinition) { - throw new BadRequestException(`type: ${requestTypeV1} requires presentationDefinition`); + requestProof.orgId = orgId; + requestProof.type = requestType; + const proofData = await this.verificationService.sendProofRequest(requestProof, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.send, + data: proofData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); } - if (requestTypeV1 === ProofRequestType.INDY) { - Validator.validateIndyProofAttributes(requestProof.proofFormats.indy.attributes); + /** + * Verify proof presentation + * @param proofId + * @param orgId + * @returns Verified proof presentation details + */ + @Post('/orgs/:orgId/proofs/:proofId/verify') + @ApiOperation({ + summary: `Verify presentation`, + description: `Verify presentation` + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async verifyPresentation( + @Res() res: Response, + @User() user: IUserRequest, + @Param('proofId') proofId: string, + @Param('orgId') orgId: string + ): Promise { + const verifyData = await this.verificationService.verifyPresentation(proofId, orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.verified, + data: verifyData + }; + return res.status(HttpStatus.CREATED).json(finalResponse); } - const version = API_Version.VERSION_1; - requestProof.version = version; - requestProof.orgId = orgId; - requestProof.type = requestTypeV1; - const proofData = await this.verificationService.sendProofRequest(requestProof, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.send, - data: proofData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * Verify proof presentation - * @param proofId The ID of the proof - * @param orgId The ID of the organization - * @returns Verified proof presentation details - */ - @Post('/orgs/:orgId/proofs/:proofId/verify') - @ApiOperation({ - summary: 'Verify presentation', - description: 'Verify the proof presentation for a the organization.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async verifyPresentation( - @Res() res: Response, - @User() user: IUserRequest, - @Param('proofId') proofId: string, - @Param('orgId') orgId: string - ): Promise { - const verifyData = await this.verificationService.verifyPresentation(proofId, orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.verified, - data: verifyData - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } - - /** - * Out-Of-Band Proof Presentation - * @param orgId The ID of the organization - * @returns Out-of-band requested proof presentation details - */ - @Post('/orgs/:orgId/proofs/oob') - @ApiOperation({ - summary: 'Sends a out-of-band proof request', - description: 'Send an out-of-band proof request for a specific organization.' - }) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) - @ApiBody({ type: SendProofRequestPayload }) - @ApiQuery({ - name: 'requestType', - enum: ProofRequestType - }) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async sendOutOfBandPresentationRequest( - @Res() res: Response, - @User() user: IUserRequest, - @Body() outOfBandRequestProof: SendProofRequestPayload, - @Param('orgId') orgId: string, - @Query('requestType') requestType: ProofRequestType = ProofRequestType.INDY - ): Promise { - user.orgId = orgId; - outOfBandRequestProof.type = requestType; - const result = await this.verificationService.sendOutOfBandPresentationRequest(outOfBandRequestProof, user); - const finalResponse: IResponseType = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.verification.success.send, - data: result - }; - return res.status(HttpStatus.CREATED).json(finalResponse); - } + /** + * Out-Of-Band Proof Presentation + * @param orgId + * @returns Out-of-band requested proof presentation details + */ + @Post('/orgs/:orgId/proofs/oob') + @ApiOperation({ + summary: `Sends a out-of-band proof request`, + description: `Sends a out-of-band proof request` + }) + @ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiBody({ type: SendProofRequestPayload }) + @ApiQuery({ + name: 'requestType', + enum: ProofRequestType + }) + @Roles(OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.VERIFIER) + @ApiBearerAuth() + @UseGuards(AuthGuard('jwt'), OrgRolesGuard) + async sendOutOfBandPresentationRequest( + @Res() res: Response, + @User() user: IUserRequest, + @Body() outOfBandRequestProof: SendProofRequestPayload, + @Param('orgId') orgId: string, + @Query('requestType') requestType:ProofRequestType = ProofRequestType.INDY + ): Promise { + user.orgId = orgId; + outOfBandRequestProof.type = requestType; + const result = await this.verificationService.sendOutOfBandPresentationRequest(outOfBandRequestProof, user); + const finalResponse: IResponseType = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.verification.success.send, + data: result + }; + return res.status(HttpStatus.CREATED).json(finalResponse); + } /** - * Receive webhook proof presentation - * @param orgId The ID of the organization + * + * @param orgId * @returns Proof presentation details */ @Post('wh/:orgId/proofs') @ApiOperation({ - summary: 'Receive webhook proof presentation', - description: 'Handle proof presentations for a specified organization via a webhook.' + summary: `Receive webhook proof presentation`, + description: `Handle proof presentations for a specified organization via a webhook` }) @ApiExcludeEndpoint() @ApiResponse({ status: HttpStatus.CREATED, description: 'Created', type: ApiResponseDto }) - @ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) - @ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) + @ApiUnauthorizedResponse({ status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized', type: UnauthorizedErrorDto }) + @ApiForbiddenResponse({ status: HttpStatus.FORBIDDEN, description: 'Forbidden', type: ForbiddenErrorDto }) async webhookProofPresentation( @Param('orgId') orgId: string, @Body() proofPresentationPayload: WebhookPresentationProofDto, @@ -445,6 +313,9 @@ export class VerificationController { ): Promise { proofPresentationPayload.type = 'Verification'; + this.logger.debug(`proof Presentation payload received ${orgId}: ${JSON.stringify(proofPresentationPayload)}`); + + if (orgId && 'default' === proofPresentationPayload.contextCorrelationId) { proofPresentationPayload.orgId = orgId; } @@ -452,7 +323,7 @@ export class VerificationController { const webhookProofPresentation = await this.verificationService .webhookProofPresentation(orgId, proofPresentationPayload) .catch((error) => { - this.logger.debug(`error in saving verification webhook ::: ${JSON.stringify(error)}`); + this.logger.error(`error in saving verification webhook ::: ${JSON.stringify(error)}`); }); const finalResponse: IResponse = { statusCode: HttpStatus.CREATED, @@ -460,57 +331,42 @@ export class VerificationController { data: webhookProofPresentation }; - const webhookUrl = await this.verificationService - ._getWebhookUrl(proofPresentationPayload?.contextCorrelationId, orgId) - .catch((error) => { - this.logger.debug(`error in getting webhook url ::: ${JSON.stringify(error)}`); - }); + const webhookUrl: string | false = webhookProofPresentation ? webhookProofPresentation.webhookUrl : false; if (webhookUrl) { await this.verificationService ._postWebhookResponse(webhookUrl, { data: proofPresentationPayload }) .catch((error) => { - this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); + this.logger.debug(`error in posting webhook response to webhook url ::: ${JSON.stringify(error)}`); }); } return res.status(HttpStatus.CREATED).json(finalResponse); } - /** - * Delete verification record - * @param orgId The ID of the organization - * @param user The user making the request - * @param res The response object - * @returns Success message - */ - @Delete('/orgs/:orgId/verification-records') - @ApiOperation({ - summary: 'Delete verification record', - description: - 'Delete all verification records associated with a specific organization by its orgId. This operation is restricted to users with the OWNER role.' - }) - @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) - @ApiBearerAuth() - @Roles(OrgRoles.OWNER) - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - async deleteVerificationRecordsByOrgId( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @User() user: user, - @Res() res: Response - ): Promise { - await this.verificationService.deleteVerificationRecords(orgId, user); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.verification.success.deleteVerificationRecord - }; - return res.status(HttpStatus.OK).json(finalResponse); - } +@Delete('/orgs/:orgId/verification-records') +@ApiOperation({ summary: 'Delete verification record', description: 'Delete verification records by orgId' }) +@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) +@ApiBearerAuth() +@Roles(OrgRoles.OWNER) +@UseGuards(AuthGuard('jwt'), OrgRolesGuard) +async deleteVerificationRecordsByOrgId( + @Param( + 'orgId', + new ParseUUIDPipe({ + exceptionFactory: (): Error => { + throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); + } + }) + ) + orgId: string, + @User() user: user, + @Res() res: Response +): Promise { + await this.verificationService.deleteVerificationRecords(orgId, user); + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.verification.success.deleteVerificationRecord + }; + return res.status(HttpStatus.OK).json(finalResponse); } +} \ No newline at end of file diff --git a/apps/api-gateway/src/verification/verification.module.ts b/apps/api-gateway/src/verification/verification.module.ts index 3f8c473af..d2769ff6f 100644 --- a/apps/api-gateway/src/verification/verification.module.ts +++ b/apps/api-gateway/src/verification/verification.module.ts @@ -5,8 +5,8 @@ import { Module } from '@nestjs/common'; import { VerificationController } from './verification.controller'; import { VerificationService } from './verification.service'; import { getNatsOptions } from '@credebl/common/nats.config'; +import { ImageServiceService } from '@credebl/image-service'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -20,6 +20,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [VerificationController], - providers: [VerificationService, NATSClient] + providers: [VerificationService, ImageServiceService] }) -export class VerificationModule {} +export class VerificationModule { } diff --git a/apps/api-gateway/src/verification/verification.service.ts b/apps/api-gateway/src/verification/verification.service.ts index 68eb70a23..3ef6eee90 100644 --- a/apps/api-gateway/src/verification/verification.service.ts +++ b/apps/api-gateway/src/verification/verification.service.ts @@ -1,147 +1,138 @@ -import { Injectable, Inject } from '@nestjs/common'; +import { Injectable, Inject} from '@nestjs/common'; +import { ClientProxy} from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; -import { SendProofRequestPayload, RequestProofDtoV1, RequestProofDtoV2 } from './dto/request-proof.dto'; +import { SendProofRequestPayload, RequestProofDto } from './dto/request-proof.dto'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { WebhookPresentationProofDto } from './dto/webhook-proof.dto'; -import { - IProofPresentationDetails, - IProofPresentationList, - IVerificationRecords -} from '@credebl/common/interfaces/verification.interface'; +import { IProofPresentationDetails, IProofPresentationList, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; import { IPresentation, IProofRequest, IProofRequestSearchCriteria } from './interfaces/verification.interface'; import { IProofPresentation } from './interfaces/verification.interface'; // To do make a similar interface in API-gateway -import { user } from '@prisma/client'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; +import { IRequestProof } from 'apps/verification/src/interfaces/verification.interface'; +// eslint-disable-next-line camelcase +import { org_agents, user } from '@prisma/client'; + @Injectable() export class VerificationService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly verificationServiceProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { - super('VerificationService'); - } - - /** - * Get all proof presentations - * @param orgId - * @returns All proof presentations details - */ - getProofPresentations( - proofRequestsSearchCriteria: IProofRequestSearchCriteria, - user: IUserRequest, - orgId: string - ): Promise { - const payload = { proofRequestsSearchCriteria, user, orgId }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'get-all-proof-presentations', payload); - } - - /** - * Get proof presentation by proofId - * @param proofId - * @param orgId - * @returns Proof presentation details by proofId - */ - getProofPresentationById(proofId: string, orgId: string, user: IUserRequest): Promise { - const payload = { proofId, orgId, user }; - return this.natsClient.sendNatsMessage( - this.verificationServiceProxy, - 'get-proof-presentations-by-proofId', - payload - ); - } - - /** - * Get verifier proof presentation by issuerId - * @param issuerId - * @returns Proof presentation details by issuerId - */ - getPresentationDetailsByIssuerId(issuerId: string, user: IUserRequest): Promise { - const payload = { issuerId, user }; - return this.natsClient.sendNatsMessage( - this.verificationServiceProxy, - 'get-proof-presentation-details-by-issuerId', - payload - ); - } + constructor( + @Inject('NATS_CLIENT') private readonly verificationServiceProxy: ClientProxy + ) { + super('VerificationService'); + } - /** - * Send proof request - * @param orgId - * @returns Requested proof presentation details - */ - sendProofRequest(requestProofDto: RequestProofDtoV1 | RequestProofDtoV2, user: IUserRequest): Promise { - const payload = { requestProofDto, user }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'send-proof-request', payload); - } + /** + * Get all proof presentations + * @param orgId + * @returns All proof presentations details + */ + getProofPresentations(proofRequestsSearchCriteria: IProofRequestSearchCriteria, user: IUserRequest, orgId: string): Promise { + const payload = { proofRequestsSearchCriteria, user, orgId }; + return this.sendNatsMessage(this.verificationServiceProxy, 'get-all-proof-presentations', payload); + } - /** - * Verify proof presentation - * @param proofId - * @param orgId - * @returns Verified proof presentation details - */ - verifyPresentation(proofId: string, orgId: string, user: IUserRequest): Promise { - const payload = { proofId, orgId, user }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'verify-presentation', payload); - } + /** + * Get proof presentation by proofId + * @param proofId + * @param orgId + * @returns Proof presentation details by proofId + */ + getProofPresentationById(proofId: string, orgId: string, user: IUserRequest): Promise { + const payload = { proofId, orgId, user }; + return this.sendNatsMessage(this.verificationServiceProxy, 'get-proof-presentations-by-proofId', payload); + } - webhookProofPresentation(orgId: string, proofPresentationPayload: WebhookPresentationProofDto): Promise { - const payload = { orgId, proofPresentationPayload }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'webhook-proof-presentation', payload); - } + /** + * Send proof request + * @param orgId + * @returns Requested proof presentation details + */ + sendProofRequest(requestProofDto: RequestProofDto, user: IUserRequest): Promise { + const requestProof: IRequestProof = { + orgId: requestProofDto.orgId, + type: requestProofDto.type, + comment: requestProofDto.comment, + autoAcceptProof: requestProofDto.autoAcceptProof, + connectionId: requestProofDto.connectionId, + goalCode: requestProofDto.goalCode, + parentThreadId: requestProofDto.parentThreadId, + protocolVersion: requestProofDto.protocolVersion, + willConfirm: requestProofDto.willConfirm + }; + if (requestProofDto.proofFormats) { + requestProof.attributes = requestProofDto.proofFormats.indy.attributes; + } + if (requestProofDto.presentationDefinition) { + requestProof.presentationDefinition = requestProofDto.presentationDefinition; + } - /** - * Out-Of-Band Proof Presentation - * @param user - * @param outOfBandRequestProof - * @returns Get out-of-band requested proof presentation details - */ - sendOutOfBandPresentationRequest( - outOfBandRequestProof: SendProofRequestPayload, - user: IUserRequest - ): Promise { - const payload = { outOfBandRequestProof, user }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'send-out-of-band-proof-request', payload); - } + const payload = { requestProof, user }; + return this.sendNatsMessage(this.verificationServiceProxy, 'send-proof-request', payload); + } - getVerifiedProofDetails(proofId: string, orgId: string, user: IUserRequest): Promise { - const payload = { proofId, orgId, user }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'get-verified-proof-details', payload); - } + /** + * Verify proof presentation + * @param proofId + * @param orgId + * @returns Verified proof presentation details + */ + verifyPresentation(proofId: string, orgId: string, user: IUserRequest): Promise { + const payload = { proofId, orgId, user }; + return this.sendNatsMessage(this.verificationServiceProxy, 'verify-presentation', payload); + } - async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { - const pattern = { cmd: 'get-webhookurl' }; - const payload = { tenantId, orgId }; + // eslint-disable-next-line camelcase + webhookProofPresentation(orgId: string, proofPresentationPayload: WebhookPresentationProofDto): Promise { + const payload = { orgId, proofPresentationPayload }; + return this.sendNatsMessage(this.verificationServiceProxy, 'webhook-proof-presentation', payload); + } - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); - return message; - } catch (error) { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw error; + /** + * Out-Of-Band Proof Presentation + * @param user + * @param outOfBandRequestProof + * @returns Get out-of-band requested proof presentation details + */ + sendOutOfBandPresentationRequest(outOfBandRequestProof: SendProofRequestPayload, user: IUserRequest): Promise { + const payload = { outOfBandRequestProof, user }; + return this.sendNatsMessage(this.verificationServiceProxy, 'send-out-of-band-proof-request', payload); + } + + getVerifiedProofDetails(proofId: string, orgId: string, user: IUserRequest): Promise { + const payload = { proofId, orgId, user }; + return this.sendNatsMessage(this.verificationServiceProxy, 'get-verified-proof-details', payload); } - } - async _postWebhookResponse(webhookUrl: string, data: object): Promise { - const pattern = { cmd: 'post-webhook-response-to-webhook-url' }; - const payload = { webhookUrl, data }; + async _getWebhookUrl(tenantId?: string, orgId?: string): Promise { + const pattern = { cmd: 'get-webhookurl' }; + const payload = { tenantId, orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw error; + } + } + + async _postWebhookResponse(webhookUrl: string, data:object): Promise { + const pattern = { cmd: 'post-webhook-response-to-webhook-url' }; + const payload = { webhookUrl, data }; - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); - return message; - } catch (error) { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw error; - } - } + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw error; + } + } - async deleteVerificationRecords(orgId: string, userDetails: user): Promise { - const payload = { orgId, userDetails }; - return this.natsClient.sendNatsMessage(this.verificationServiceProxy, 'delete-verification-records', payload); - } + async deleteVerificationRecords(orgId: string, userDetails: user): Promise { + const payload = { orgId, userDetails }; + return this.sendNatsMessage(this.verificationServiceProxy, 'delete-verification-records', payload); + } } diff --git a/apps/api-gateway/src/webhook/dtos/get-webhoook-dto.ts b/apps/api-gateway/src/webhook/dtos/get-webhoook-dto.ts index 846225df0..31b1d8749 100644 --- a/apps/api-gateway/src/webhook/dtos/get-webhoook-dto.ts +++ b/apps/api-gateway/src/webhook/dtos/get-webhoook-dto.ts @@ -1,5 +1,5 @@ import { ApiExtraModels, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString, IsUUID } from 'class-validator'; +import { IsOptional, IsString } from 'class-validator'; import { Transform } from 'class-transformer'; import { trim } from '@credebl/common/cast.helper'; @@ -15,7 +15,6 @@ export class GetWebhookDto { @ApiPropertyOptional({example: '3a041d6e-d24c-4ed9-b011-1cfc371a8b8e'}) @IsOptional() @Transform(({ value }) => trim(value)) - @IsUUID('4', { message: 'Please provide valid tenantId' }) @IsString({ message: 'Tenant id must be in string format.' }) tenantId?: string; } \ No newline at end of file diff --git a/apps/api-gateway/src/webhook/webhook.controller.ts b/apps/api-gateway/src/webhook/webhook.controller.ts index c35843f02..9d343ff77 100644 --- a/apps/api-gateway/src/webhook/webhook.controller.ts +++ b/apps/api-gateway/src/webhook/webhook.controller.ts @@ -24,15 +24,19 @@ import { ApiForbiddenResponse, ApiUnauthorizedResponse, ApiBearerAuth + } from '@nestjs/swagger'; import { AuthGuard } from '@nestjs/passport'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UnauthorizedErrorDto } from '../dtos/unauthorized-error.dto'; import { ForbiddenErrorDto } from '../dtos/forbidden-error.dto'; + import { Response } from 'express'; -import { IResponse } from '@credebl/common/interfaces/response.interface'; +import { IResponse } from '@credebl/common/interfaces/response.interface'; import { WebhookService } from './webhook.service'; -import { RegisterWebhookDto } from './dtos/register-webhook-dto'; +import { + RegisterWebhookDto +} from './dtos/register-webhook-dto'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CustomExceptionFilter } from 'apps/api-gateway/common/exception-handler'; import { OrgRolesGuard } from '../authz/guards/org-roles.guard'; @@ -43,83 +47,60 @@ import { GetWebhookDto } from './dtos/get-webhoook-dto'; @UseFilters(CustomExceptionFilter) @Controller('webhooks') @ApiTags('webhooks') -@ApiUnauthorizedResponse({ description: 'Unauthorized', type: UnauthorizedErrorDto }) -@ApiForbiddenResponse({ description: 'Forbidden', type: ForbiddenErrorDto }) +@ApiUnauthorizedResponse({ status: 401, description: 'Unauthorized', type: UnauthorizedErrorDto }) +@ApiForbiddenResponse({ status: 403, description: 'Forbidden', type: ForbiddenErrorDto }) export class WebhookController { - constructor(private readonly webhookService: WebhookService) {} + constructor( + private readonly webhookService: WebhookService + + ) {} private readonly logger = new Logger('WebhookController'); private readonly PAGE: number = 1; - /** - * Register a webhook URL for an organization - * @param orgId The ID of the organization - * @param registerWebhookDto The webhook registration details - * @param res The response object - * @returns The registered webhook details - */ - @Post('/orgs/:orgId/register') - @ApiOperation({ - summary: 'Register Webhook', - description: 'Register a webhook URL for an organization.' - }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ADMIN) - @ApiResponse({ status: HttpStatus.CREATED, description: 'Webhook URL registered successfully', type: ApiResponseDto }) - async registerWebhook( - @Param( - 'orgId', - new ParseUUIDPipe({ - exceptionFactory: (): Error => { - throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); - } - }) - ) - orgId: string, - @Body() registerWebhookDto: RegisterWebhookDto, - @Res() res: Response - ): Promise { - registerWebhookDto.orgId = orgId; + +@Post('/orgs/:orgId/register') +@ApiOperation({ +summary: 'Register Webhook', +description: 'Register a webhook url' +}) +@ApiBearerAuth() +@UseGuards(AuthGuard('jwt'), OrgRolesGuard) +@Roles(OrgRoles.OWNER, OrgRoles.ADMIN) +@ApiResponse({ status: HttpStatus.CREATED, description: 'Success', type: ApiResponseDto }) +async registerWebhook(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Body() registerWebhookDto: RegisterWebhookDto, +@Res() res: Response): Promise { +registerWebhookDto.orgId = orgId; - const webhookRegisterDetails = await this.webhookService.registerWebhook(registerWebhookDto); +const webhookRegisterDetails = await this.webhookService.registerWebhook(registerWebhookDto); - const finalResponse: IResponse = { - statusCode: HttpStatus.CREATED, - message: ResponseMessages.agent.success.webhookUrlRegister, - data: webhookRegisterDetails - }; +const finalResponse: IResponse = { + statusCode: HttpStatus.CREATED, + message: ResponseMessages.agent.success.webhookUrlRegister, + data: webhookRegisterDetails +}; - return res.status(HttpStatus.CREATED).json(finalResponse); - } +return res.status(HttpStatus.CREATED).json(finalResponse); +} - /** - * Get the webhook URL details for an organization - * @param getWebhook The webhook query parameters - * @param res The response object - * @returns The webhook URL details - */ - @Get('/orgs/webhookurl') - @ApiOperation({ - summary: 'Get Webhook URL Details', - description: 'Retrieve the details of the webhook URL for an organization.' - }) - @ApiBearerAuth() - @UseGuards(AuthGuard('jwt'), OrgRolesGuard) - @Roles(OrgRoles.OWNER, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.ADMIN) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Webhook URL details retrieved successfully', - type: ApiResponseDto - }) - async getWebhookUrl(@Query() getWebhook: GetWebhookDto, @Res() res: Response): Promise { - const webhookUrlData = await this.webhookService.getWebhookUrl(getWebhook); - const finalResponse: IResponse = { - statusCode: HttpStatus.OK, - message: ResponseMessages.agent.success.getWebhookUrl, - data: webhookUrlData - }; +@Get('/orgs/webhookurl') +@ApiOperation({ + summary: 'Get the webhookurl details', + description: 'Get the webhookurl details' +}) +@ApiBearerAuth() +@UseGuards(AuthGuard('jwt')) +@Roles(OrgRoles.OWNER, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.ADMIN) +async getWebhookUrl(@Query() getWebhook: GetWebhookDto, @Res() res: Response): Promise { + const webhookUrlData = await this.webhookService.getWebhookUrl(getWebhook); + + const finalResponse: IResponse = { + statusCode: HttpStatus.OK, + message: ResponseMessages.agent.success.getWebhookUrl, + data: webhookUrlData + }; + + return res.status(HttpStatus.OK).json(finalResponse); +} - return res.status(HttpStatus.OK).json(finalResponse); - } } diff --git a/apps/api-gateway/src/webhook/webhook.module.ts b/apps/api-gateway/src/webhook/webhook.module.ts index 08433c3f9..db38e1f7e 100644 --- a/apps/api-gateway/src/webhook/webhook.module.ts +++ b/apps/api-gateway/src/webhook/webhook.module.ts @@ -7,7 +7,6 @@ import { HttpModule } from '@nestjs/axios'; import { getNatsOptions } from '@credebl/common/nats.config'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -21,6 +20,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]) ], controllers: [WebhookController], - providers: [WebhookService, CommonService, AwsService, NATSClient] + providers: [WebhookService, CommonService, AwsService] }) export class WebhookModule { } diff --git a/apps/api-gateway/src/webhook/webhook.service.ts b/apps/api-gateway/src/webhook/webhook.service.ts index d3e00925d..faca09b72 100644 --- a/apps/api-gateway/src/webhook/webhook.service.ts +++ b/apps/api-gateway/src/webhook/webhook.service.ts @@ -1,30 +1,28 @@ /* eslint-disable camelcase */ import { Injectable, Inject } from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { RegisterWebhookDto } from './dtos/register-webhook-dto'; import { ICreateWebhookUrl, IGetWebhookUrl } from 'apps/webhook/interfaces/webhook.interfaces'; import { GetWebhookDto } from './dtos/get-webhoook-dto'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { ClientProxy } from '@nestjs/microservices'; @Injectable() export class WebhookService extends BaseService { - constructor( - @Inject('NATS_CLIENT') private readonly webhookProxy: ClientProxy, - private readonly natsClient: NATSClient - ) { + constructor(@Inject('NATS_CLIENT') private readonly webhookProxy: ClientProxy) { super('WebhookService'); } - async getWebhookUrl(getWebhook: GetWebhookDto): Promise { + async getWebhookUrl(getWebhook: GetWebhookDto): Promise { // NATS call - return this.natsClient.sendNatsMessage(this.webhookProxy, 'get-webhookurl', getWebhook); + return this.sendNatsMessage(this.webhookProxy, 'get-webhookurl', getWebhook); } async registerWebhook(registerWebhookDto: RegisterWebhookDto): Promise { - const payload = { registerWebhookDto }; + const payload = { registerWebhookDto}; // NATS call - return this.natsClient.sendNatsMessage(this.webhookProxy, 'register-webhook', payload); + return this.sendNatsMessage(this.webhookProxy, 'register-webhook', payload); } + + } diff --git a/apps/cloud-wallet/src/cloud-wallet.controller.ts b/apps/cloud-wallet/src/cloud-wallet.controller.ts index e639ec511..b9768d17f 100644 --- a/apps/cloud-wallet/src/cloud-wallet.controller.ts +++ b/apps/cloud-wallet/src/cloud-wallet.controller.ts @@ -2,28 +2,10 @@ import { Controller } from '@nestjs/common'; // Import the common service in the library import { CloudWalletService } from './cloud-wallet.service'; // Import the common service in connection module import { MessagePattern } from '@nestjs/microservices'; // Import the nestjs microservices package -import { - IAcceptOffer, - ICreateCloudWalletDid, - IReceiveInvitation, - IAcceptProofRequest, - IProofRequestRes, - ICloudBaseWalletConfigure, - ICreateCloudWallet, - IGetProofPresentation, - IGetProofPresentationById, - IGetStoredWalletInfo, - IStoredWalletDetails, - ICreateConnection, - IConnectionInvitationResponse, - IWalletDetailsForDidList, - IConnectionDetailsById, - ITenantDetail, - ICredentialDetails, - GetAllCloudWalletConnections, - IBasicMessage, - IBasicMessageDetails -} from '@credebl/common/interfaces/cloud-wallet.interface'; +import { IAcceptOffer, ICreateCloudWalletDid, IReceiveInvitation, IAcceptProofRequest, IProofRequestRes, ICloudBaseWalletConfigure, ICreateCloudWallet, IGetProofPresentation, IGetProofPresentationById, IGetStoredWalletInfo, IStoredWalletDetails, ICreateConnection, IConnectionInvitationResponse, IWalletDetailsForDidList, IConnectionDetailsById, ITenantDetail, ICredentialDetails, GetAllCloudWalletConnections, IBasicMessage, IBasicMessageDetails, IProofPresentationDetails, IGetCredentialsForRequest, ICredentialForRequestRes, IProofPresentationPayloadWithCred, IDeclineProofRequest, BaseAgentInfo, ISelfAttestedCredential, IW3cCredentials, ICheckCloudWalletStatus, IDeleteCloudWallet, IExportCloudWallet, IAddConnectionType } from '@credebl/common/interfaces/cloud-wallet.interface'; +// eslint-disable-next-line camelcase +import { cloud_wallet_user_info, user as User } from '@prisma/client'; +import { UpdateBaseWalletDto } from 'apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto'; @Controller() export class CloudWalletController { @@ -39,16 +21,36 @@ export class CloudWalletController { return this.cloudWalletService.createConnection(createConnection); } + @MessagePattern({ cmd: 'check-cloud-wallet-status' }) + async checkCloudWalletStatus(createConnection: ICheckCloudWalletStatus): Promise { + return this.cloudWalletService.checkCloudWalletStatus(createConnection); + } + @MessagePattern({ cmd: 'accept-proof-request-by-holder' }) async acceptProofRequest(acceptProofRequestPayload: IAcceptProofRequest): Promise { return this.cloudWalletService.acceptProofRequest(acceptProofRequestPayload); } + + @MessagePattern({ cmd: 'decline-proof-request-by-holder' }) + async declineProofRequest(declineProofRequestPayload: IDeclineProofRequest): Promise { + return this.cloudWalletService.declineProofRequest(declineProofRequestPayload); + } @MessagePattern({ cmd: 'get-proof-by-proof-id-holder' }) async getProofById(proofPrsentationByIdPayload: IGetProofPresentationById): Promise { return this.cloudWalletService.getProofById(proofPrsentationByIdPayload); } + @MessagePattern({ cmd: 'submit-proof-with-cred' }) + async submitProofWithCred(proofPresentationByIdPayload: IProofPresentationPayloadWithCred): Promise { + return this.cloudWalletService.submitProofWithCred(proofPresentationByIdPayload); + } + + @MessagePattern({ cmd: 'get-credentials-for-request' }) + async getCredentialsForRequest(proofPrsentationByIdPayload: IGetCredentialsForRequest): Promise { + return this.cloudWalletService.getCredentialsByProofId(proofPrsentationByIdPayload); + } + @MessagePattern({ cmd: 'get-proof-presentation-holder' }) async getProofPresentation(proofPresentationPayload: IGetProofPresentation): Promise { return this.cloudWalletService.getProofPresentation(proofPresentationPayload); @@ -58,6 +60,22 @@ export class CloudWalletController { async createConnectionInvitation(cloudWalletDetails: ICreateCloudWallet): Promise { return this.cloudWalletService.createCloudWallet(cloudWalletDetails); } + + @MessagePattern({ cmd: 'delete-cloud-wallet' }) + // eslint-disable-next-line camelcase + async deleteCloudWallet(cloudWalletDetails: IDeleteCloudWallet): Promise { + return this.cloudWalletService.deleteCloudWallet(cloudWalletDetails); + } + + @MessagePattern({ cmd: 'get-base-wallet-details' }) + async getBaseWalletDetails(user:User): Promise { + return this.cloudWalletService.getBaseWalletDetails(user); + } + + @MessagePattern({ cmd: 'update-base-wallet-details' }) + async updateBaseWalletDetails(updateBaseWalletDto:UpdateBaseWalletDto): Promise { + return this.cloudWalletService.updateBaseWalletDetails(updateBaseWalletDto); + } @MessagePattern({ cmd: 'receive-invitation-by-url' }) async receiveInvitationByUrl(ReceiveInvitationDetails: IReceiveInvitation): Promise { @@ -73,9 +91,15 @@ export class CloudWalletController { async createDid(createDidDetails: ICreateCloudWalletDid): Promise { return this.cloudWalletService.createDid(createDidDetails); } + + + @MessagePattern({ cmd: 'export-cloud-wallet' }) + async exportCloudWallet(exportWallet: IExportCloudWallet): Promise { + return this.cloudWalletService.exportCloudWallet(exportWallet); + } @MessagePattern({ cmd: 'cloud-wallet-did-list' }) - async getDidList(walletDetails: IWalletDetailsForDidList): Promise { + async getDidList(walletDetails: IWalletDetailsForDidList): Promise { return this.cloudWalletService.getDidList(walletDetails); } @@ -84,6 +108,11 @@ export class CloudWalletController { return this.cloudWalletService.getconnectionById(connectionDetails); } + @MessagePattern({ cmd: 'Add-cloud-wallet-connection--type-by-id' }) + async addConnectionTypeById(connectionDetails: IAddConnectionType): Promise { + return this.cloudWalletService.AddConnectionTypeById(connectionDetails); + } + @MessagePattern({ cmd: 'get-all-cloud-wallet-connections-list-by-id' }) async getAllconnectionById(connectionDetails: GetAllCloudWalletConnections): Promise { return this.cloudWalletService.getAllconnectionById(connectionDetails); @@ -94,11 +123,42 @@ export class CloudWalletController { return this.cloudWalletService.getCredentialListById(tenantDetails); } + @MessagePattern({ cmd: 'get-all-w3c-credenentials' }) + async getAllW3cCredentials(w3cCredential: IW3cCredentials): Promise { + return this.cloudWalletService.getAllW3cCredentials(w3cCredential); + } + @MessagePattern({ cmd: 'wallet-credential-by-record-id' }) async getCredentialByCredentialRecordId(credentialDetails: ICredentialDetails): Promise { return this.cloudWalletService.getCredentialByRecord(credentialDetails); } + @MessagePattern({ cmd: 'get-w3c-credential-by-record-id' }) + async getW3cCredentialByRecordId(w3cCredential: IW3cCredentials): Promise { + return this.cloudWalletService.getW3cCredentialByRecordId(w3cCredential); + } + + @MessagePattern({ cmd: 'wallet-credentialFormatData-by-record-id' }) + async getCredentialFormatDataByCredentialRecordId(proofDetails: ICredentialDetails): Promise { + return this.cloudWalletService.getCredentialFormatDataByRecord(proofDetails); + } + + @MessagePattern({ cmd: 'wallet-Proof-presentation-FormatData-by-record-id' }) + async getProofPresentationFormatDataByCredentialRecordId(credentialDetails: IProofPresentationDetails): Promise { + return this.cloudWalletService.getProofFormDataByRecord(credentialDetails); + } + + + @MessagePattern({ cmd: 'delete-credential-by-record-id' }) + async deleteCredentialByCredentialRecordId(credentialDetails: ICredentialDetails): Promise { + return this.cloudWalletService.deleteCredentialByRecord(credentialDetails); + } + + @MessagePattern({ cmd: 'delete-w3c-credential-by-record-id' }) + async deleteW3cCredentialByCredentialRecordId(credentialDetails: ICredentialDetails): Promise { + return this.cloudWalletService.deleteW3cCredentialByRecord(credentialDetails); + } + @MessagePattern({ cmd: 'basic-message-list-by-connection-id' }) async getBasicMessageByConnectionId(connectionDetails: IBasicMessage): Promise { return this.cloudWalletService.getBasicMessageByConnectionId(connectionDetails); @@ -108,4 +168,10 @@ export class CloudWalletController { async sendBasicMessage(messageDetails: IBasicMessageDetails): Promise { return this.cloudWalletService.sendBasicMessage(messageDetails); } -} + + @MessagePattern({ cmd: 'create-self-attested-w3c-credential' }) + async createSelfAttestedW3cCredential(selfAttestedCredential: ISelfAttestedCredential): Promise { + return this.cloudWalletService.createSelfAttestedW3cCredential(selfAttestedCredential); + } + +} \ No newline at end of file diff --git a/apps/cloud-wallet/src/cloud-wallet.module.ts b/apps/cloud-wallet/src/cloud-wallet.module.ts index 32f733734..895aac9a2 100644 --- a/apps/cloud-wallet/src/cloud-wallet.module.ts +++ b/apps/cloud-wallet/src/cloud-wallet.module.ts @@ -7,39 +7,21 @@ import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; import { PrismaService } from '@credebl/prisma-service'; import { CloudWalletRepository } from './cloud-wallet.repository'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; @Module({ imports: [ - ClientsModule.register([ - { - name: 'NATS_CLIENT', - transport: Transport.NATS, - options: getNatsOptions(process.env.CLOUD_WALLET_NKEY_SEED) - } - ]), - - CommonModule, - GlobalConfigModule, - LoggerModule, - PlatformConfig, - ContextInterceptorModule, - CacheModule.register() - ], - controllers: [CloudWalletController], - providers: [ - CloudWalletService, - CloudWalletRepository, - PrismaService, - Logger, +ClientsModule.register([ { - provide: MICRO_SERVICE_NAME, - useValue: 'cloud-wallet' + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: getNatsOptions(process.env.CLOUD_WALLET_NKEY_SEED) } - ] + ]), + + CommonModule, + CacheModule.register() +], + controllers: [CloudWalletController], + providers: [CloudWalletService, CloudWalletRepository, PrismaService, Logger] }) export class CloudWalletModule {} diff --git a/apps/cloud-wallet/src/cloud-wallet.repository.ts b/apps/cloud-wallet/src/cloud-wallet.repository.ts index cbf63f788..83abc3524 100644 --- a/apps/cloud-wallet/src/cloud-wallet.repository.ts +++ b/apps/cloud-wallet/src/cloud-wallet.repository.ts @@ -2,8 +2,9 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { CloudWalletType } from '@credebl/enum/enum'; // eslint-disable-next-line camelcase -import { cloud_wallet_user_info, user } from '@prisma/client'; -import { ICloudWalletDetails, IGetStoredWalletInfo, IStoredWalletDetails, IStoreWalletInfo } from '@credebl/common/interfaces/cloud-wallet.interface'; +import { cloud_wallet_user_info, Prisma, user } from '@prisma/client'; +import { BaseAgentInfo, ICloudWalletDetails, IGetStoredWalletInfo, IStoredWalletDetails, IStoreWalletInfo } from '@credebl/common/interfaces/cloud-wallet.interface'; +import { UpdateBaseWalletDto } from 'apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto'; @Injectable() @@ -15,11 +16,12 @@ export class CloudWalletRepository { // eslint-disable-next-line camelcase - async getCloudWalletDetails(type: CloudWalletType): Promise { + async getCloudWalletDetails(type: CloudWalletType): Promise { try { - const agentDetails = await this.prisma.cloud_wallet_user_info.findFirstOrThrow({ + const agentDetails = await this.prisma.cloud_wallet_user_info.findFirst({ where: { - type + type, + isActive:true } }); return agentDetails; @@ -28,13 +30,80 @@ export class CloudWalletRepository { throw error; } } + + + // eslint-disable-next-line camelcase + async getBaseWalletDetails(type: CloudWalletType, user:user): Promise { + try { + const agentDetails = await this.prisma.cloud_wallet_user_info.findMany({ + where: { + type, + isActive:true, + createdBy:user.id + }, + select: { + id:true, + agentEndpoint:true, + useCount:true, + maxSubWallets:true + } + }); + return agentDetails; + } catch (error) { + this.logger.error(`Error in getCloudWalletBaseAgentDetails: ${error.message}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async updateBaseWalletDetails(type: CloudWalletType, updateBaseWalletDto:UpdateBaseWalletDto): Promise { + try { + const agentDetails = await this.prisma.cloud_wallet_user_info.update({ + where: { + id:updateBaseWalletDto.walletId, + createdBy:updateBaseWalletDto.userId + }, + data: { + isActive: updateBaseWalletDto.isActive, + maxSubWallets:updateBaseWalletDto.maxSubWallets + }, + select:{ + id:true, + agentEndpoint:true, + useCount:true, + maxSubWallets:true + } + }); + return agentDetails; + } catch (error) { + this.logger.error(`Error in UpdateCloudWalletBaseAgentDetails: ${error.message}`); + throw error; + } + } + + async updateCloudWalletDetails( + where: Prisma.cloud_wallet_user_infoWhereUniqueInput, + data: Prisma.cloud_wallet_user_infoUpdateInput + // eslint-disable-next-line camelcase + ): Promise { + try { + const agentDetails = await this.prisma.cloud_wallet_user_info.update({ + where, + data + }); + return agentDetails; + } catch (error) { + this.logger.error(`Error in UpdateCloudWalletAgentDetails: ${error.message}`); + throw error; + } + } // eslint-disable-next-line camelcase - async checkUserExist(email: string): Promise { + async checkUserExist(userId: string): Promise { try { const agentDetails = await this.prisma.cloud_wallet_user_info.findUnique({ where: { - email + userId } }); return agentDetails; @@ -80,11 +149,11 @@ export class CloudWalletRepository { } // eslint-disable-next-line camelcase - async getCloudWalletInfo(email: string): Promise { + async getCloudWalletInfo(userId: string): Promise { try { const walletInfoData = await this.prisma.cloud_wallet_user_info.findUnique({ where: { - email + userId } }); return walletInfoData; @@ -96,7 +165,7 @@ export class CloudWalletRepository { async storeCloudWalletInfo(cloudWalletInfoPayload: IStoreWalletInfo): Promise { try { - const { agentEndpoint, agentApiKey, email, type, userId, key, createdBy, lastChangedBy } = cloudWalletInfoPayload; + const { agentEndpoint, agentApiKey, email, type, userId, key, createdBy, lastChangedBy, maxSubWallets } = cloudWalletInfoPayload; const walletInfoData = await this.prisma.cloud_wallet_user_info.create({ data: { type, @@ -106,7 +175,8 @@ export class CloudWalletRepository { userId, key, createdBy, - lastChangedBy + lastChangedBy, + maxSubWallets }, select: { id: true, @@ -138,6 +208,25 @@ export class CloudWalletRepository { } } + // eslint-disable-next-line camelcase + async deleteCloudSubWallet(userId: string): Promise { + try { + + + const res = this.prisma.cloud_wallet_user_info.delete({ + where: { + userId + } + }); + + + return res; + } catch (error) { + this.logger.error(`Error in getCloudSubWallet: ${error}`); + throw error; + } + } + async getUserInfo(email: string): Promise { try { const userDetails = await this.prisma.user.findUnique({ @@ -151,4 +240,5 @@ export class CloudWalletRepository { throw error; } } + } diff --git a/apps/cloud-wallet/src/cloud-wallet.service.ts b/apps/cloud-wallet/src/cloud-wallet.service.ts index 0a396251a..ba6bacf96 100644 --- a/apps/cloud-wallet/src/cloud-wallet.service.ts +++ b/apps/cloud-wallet/src/cloud-wallet.service.ts @@ -3,12 +3,14 @@ import { CommonService } from '@credebl/common'; import { BadRequestException, ConflictException, + HttpStatus, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { ClientProxy, RpcException } from '@nestjs/microservices'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { @@ -30,17 +32,33 @@ import { IConnectionDetailsById, ITenantDetail, ICredentialDetails, - ICreateConnection, + ICreateConnection, IConnectionInvitationResponse, GetAllCloudWalletConnections, IBasicMessage, - IBasicMessageDetails + IBasicMessageDetails, + IProofPresentationDetails, + IGetCredentialsForRequest, + ICredentialForRequestRes, + IProofPresentationPayloadWithCred, + IDeclineProofRequest, + BaseAgentInfo, + ISelfAttestedCredential, + IW3cCredentials, + IDeleteCloudWallet, + ICheckCloudWalletStatus, + IExportCloudWallet, + IAddConnectionType } from '@credebl/common/interfaces/cloud-wallet.interface'; import { CloudWalletRepository } from './cloud-wallet.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { CloudWalletType } from '@credebl/enum/enum'; import { CommonConstants } from '@credebl/common/common.constant'; -import { ClientProxy } from '@nestjs/microservices'; +import { cloud_wallet_user_info, user } from '@prisma/client'; +import { UpdateBaseWalletDto } from 'apps/api-gateway/src/cloud-wallet/dtos/cloudWallet.dto'; +// import { ClientRegistrationService } from '@credebl/client-registration'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const md5 = require('md5'); @Injectable() export class CloudWalletService { @@ -49,7 +67,6 @@ export class CloudWalletService { @Inject('NATS_CLIENT') private readonly cloudWalletServiceProxy: ClientProxy, private readonly cloudWalletRepository: CloudWalletRepository, private readonly logger: Logger, - // TODO: Remove duplicate, unused variable @Inject(CACHE_MANAGER) private cacheService: Cache ) {} @@ -59,14 +76,21 @@ export class CloudWalletService { * @returns cloud base wallet */ async configureBaseWallet(configureBaseWalletPayload: ICloudBaseWalletConfigure): Promise { - const { agentEndpoint, apiKey, email, walletKey, userId } = configureBaseWalletPayload; + const { agentEndpoint, apiKey, email, walletKey, userId, maxSubWallets } = configureBaseWalletPayload; try { - const existingWalletInfo = await this.cloudWalletRepository.getCloudWalletInfo(email); - if (existingWalletInfo) { - throw new ConflictException(ResponseMessages.cloudWallet.error.agentAlreadyExist); + const getAgentInfo = await this.commonService.httpGet( + `${agentEndpoint}${CommonConstants.URL_AGENT_GET_ENDPOINT}` + ); + if (!getAgentInfo?.isInitialized) { + throw new BadRequestException(ResponseMessages.cloudWallet.error.notReachable); } + // const existingWalletInfo = await this.cloudWalletRepository.getCloudWalletInfo(userId); + // if (existingWalletInfo) { + // throw new ConflictException(ResponseMessages.cloudWallet.error.agentAlreadyExist); + // } + const [encryptionWalletKey, encryptionApiKey] = await Promise.all([ this.commonService.dataEncryption(walletKey), this.commonService.dataEncryption(apiKey) @@ -77,10 +101,11 @@ export class CloudWalletService { agentApiKey: encryptionApiKey, email, type: CloudWalletType.BASE_WALLET, - userId, + userId: null, key: encryptionWalletKey, createdBy: userId, - lastChangedBy: userId + lastChangedBy: userId, + maxSubWallets }; const storedWalletInfo = await this.cloudWalletRepository.storeCloudWalletInfo(walletInfoToStore); @@ -91,25 +116,52 @@ export class CloudWalletService { } } + /** + * Check cloud wallet status + * @param checkCloudWalletStatus + * @returns connection details + */ + async checkCloudWalletStatus(checkCloudWalletStatus: ICheckCloudWalletStatus): Promise { + try { + + const { userId } = checkCloudWalletStatus; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + delete checkCloudWalletStatus.email; + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CHECK_CLOUD_WALLET_EXISTS}/${tenantId}`; + + const checkCloudWalletExists = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return checkCloudWalletExists; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + /** * Create connection - * @param createConnection + * @param createConnection * @returns connection details */ async createConnection(createConnection: ICreateConnection): Promise { try { + const { userId, ...connectionPayload } = createConnection; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - delete connectionPayload.email; - const { agentEndpoint } = baseWalletDetails; + delete connectionPayload.email; - const url = `${agentEndpoint}${CommonConstants.URL_CONN_INVITE}`; + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; - const createConnectionDetails = await this.commonService.httpPost(url, connectionPayload, { - headers: { authorization: decryptedApiKey } - }); - return createConnectionDetails; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREATE_CONNECTION_INVITATION}/${tenantId}`; + + const createConnectionDetails = await this.commonService.httpPost(url, connectionPayload, { headers: { authorization: decryptedApiKey } }); + return createConnectionDetails; } catch (error) { await this.commonService.handleError(error); throw error; @@ -125,10 +177,12 @@ export class CloudWalletService { const { proofRecordId, comment, filterByNonRevocationRequirements, filterByPresentationPreview, userId } = acceptProofRequest; try { - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}${CommonConstants.CLOUD_WALLET_ACCEPT_PROOF_REQUEST}`; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}${CommonConstants.CLOUD_WALLET_ACCEPT_PROOF_REQUEST}${tenantId}`; const proofAcceptRequestPayload = { comment, filterByNonRevocationRequirements, @@ -145,6 +199,36 @@ export class CloudWalletService { } } + /** + * Decline proof request + * @param declineProofRequest + * @returns proof presentation + */ + async declineProofRequest(declineProofRequest: IDeclineProofRequest): Promise { + const { proofRecordId, sendProblemReport, problemReportDescription, userId } = + declineProofRequest; + try { + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}${CommonConstants.CLOUD_WALLET_DECLINE_PROOF_REQUEST}${tenantId}`; + const proofAcceptRequestPayload = { + sendProblemReport, + problemReportDescription + }; + + const declineProofRequest = await this.commonService.httpPost(url, proofAcceptRequestPayload, { + headers: { authorization: decryptedApiKey } + }); + return declineProofRequest; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + /** * Get proof presentation by proof Id * @param proofPrsentationByIdPayload @@ -153,9 +237,12 @@ export class CloudWalletService { async getProofById(proofPrsentationByIdPayload: IGetProofPresentationById): Promise { try { const { proofRecordId, userId } = proofPrsentationByIdPayload; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}}`; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${proofRecordId}/${tenantId}`; const getProofById = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return getProofById; @@ -165,6 +252,56 @@ export class CloudWalletService { } } + /** + * Get proof presentation by proof Id + * @param proofPrsentationByIdPayload + * @returns proof presentation + */ + async submitProofWithCred(proofPresentationByIdPayload: IProofPresentationPayloadWithCred): Promise { + try { + const { proof, userId } = proofPresentationByIdPayload; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_POST_PROOF_REQUEST_WITH_CRED}/${tenantId}`; + + const submitProofWithCred = await this.commonService.httpPost(url, proof, { headers: { authorization: decryptedApiKey } }); + return submitProofWithCred; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + + /** + * Get Credentials by proof Id + * @param proofPrsentationByIdPayload + * @returns proof presentation + */ + async getCredentialsByProofId(proofPrsentationByIdPayload: IGetCredentialsForRequest): Promise { + try { + const { proofRecordId, userId } = proofPrsentationByIdPayload; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_CREDENTIALS_BY_PROOF_REQUEST}/${tenantId}/${proofRecordId}`; + + const getProofById = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return getProofById; + } catch (error) { + if (error.response?.error?.message?.includes('Proof record is in invalid state')) { + throw new RpcException({message:error.response.error.message, statusCode: HttpStatus.BAD_REQUEST}); + } + await this.commonService.handleError(error); + + throw error; + } + } + /** * Get proof presentation * @param proofPresentationPayload @@ -174,10 +311,15 @@ export class CloudWalletService { try { const { threadId, userId } = proofPresentationPayload; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; - const threadParam = threadId ? `?threadId=${threadId}` : ''; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${threadParam}}`; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_GET_PROOF_REQUEST}/${tenantId}${ + threadId ? `?threadId=${threadId}` : '' + }`; + const getProofById = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return getProofById; } catch (error) { @@ -192,28 +334,27 @@ export class CloudWalletService { * @returns cloud wallet info */ async _commonCloudWalletInfo(userId: string): Promise<[CloudWallet, string]> { - const baseWalletDetails = await this.cloudWalletRepository.getCloudWalletDetails(CloudWalletType.BASE_WALLET); + + const getTenant = await this.cloudWalletRepository.getCloudSubWallet(userId); - if (!baseWalletDetails) { - throw new NotFoundException(ResponseMessages.cloudWallet.error.notFoundBaseWallet); + if (!getTenant) { + throw new NotFoundException(ResponseMessages.cloudWallet.error.walletRecordNotFound); } const getAgentDetails = await this.commonService.httpGet( - `${baseWalletDetails?.agentEndpoint}${CommonConstants.URL_AGENT_GET_ENDPOINT}` + `${getTenant?.agentEndpoint}${CommonConstants.URL_AGENT_GET_ENDPOINT}` ); if (!getAgentDetails?.isInitialized) { throw new BadRequestException(ResponseMessages.cloudWallet.error.notReachable); } - - const getTenant = await this.cloudWalletRepository.getCloudSubWallet(userId); - + if (!getTenant || !getTenant?.tenantId) { throw new NotFoundException(ResponseMessages.cloudWallet.error.walletRecordNotFound); } const decryptedApiKey = await this.commonService.decryptPassword(getTenant?.agentApiKey); - return [baseWalletDetails, decryptedApiKey]; + return [getTenant, decryptedApiKey]; } /** @@ -231,18 +372,34 @@ export class CloudWalletService { } }; - const checkUserExist = await this.cloudWalletRepository.checkUserExist(email); + const checkUserExist = await this.cloudWalletRepository.checkUserExist(userId); if (checkUserExist) { throw new ConflictException(ResponseMessages.cloudWallet.error.userExist); } - + const baseWalletDetails = await this.cloudWalletRepository.getCloudWalletDetails(CloudWalletType.BASE_WALLET); - const { agentEndpoint, agentApiKey } = baseWalletDetails; - if (!agentEndpoint || !agentApiKey) { - throw new NotFoundException(ResponseMessages.cloudWallet.error.notFoundBaseWallet); + if (!baseWalletDetails) { + throw new InternalServerErrorException(ResponseMessages.cloudWallet.error.BaseWalletLimitExceeded, { + cause: new Error(), + description: ResponseMessages.errorMessages.serverError + }); + } else { + const lastSubWallet: boolean = baseWalletDetails.useCount >= baseWalletDetails.maxSubWallets - 1; + await this.cloudWalletRepository.updateCloudWalletDetails({ + id: baseWalletDetails.id + }, + { + useCount : { + increment: 1 + }, + ...(lastSubWallet && { isActive : false }) + } + ); } + + const { agentEndpoint, agentApiKey } = baseWalletDetails; const url = `${agentEndpoint}${CommonConstants.URL_SHAGENT_CREATE_TENANT}`; const decryptedApiKey = await this.commonService.decryptPassword(agentApiKey); @@ -262,7 +419,7 @@ export class CloudWalletService { }); } - const walletKey = await this.commonService.dataEncryption(createCloudWalletResponse.config.walletConfig.token); + const walletKey = await this.commonService.dataEncryption(createCloudWalletResponse.config.walletConfig.key); if (!walletKey) { throw new BadRequestException(ResponseMessages.cloudWallet.error.encryptCloudWalletKey, { @@ -278,7 +435,7 @@ export class CloudWalletService { tenantId: createCloudWalletResponse.id, type: CloudWalletType.SUB_WALLET, userId, - agentApiKey: this.commonService.dataEncryption(createCloudWalletResponse.token), + agentApiKey, agentEndpoint, email, key: walletKey, @@ -292,6 +449,59 @@ export class CloudWalletService { } } + /** + * Create clous wallet + * @param cloudWalletDetails + * @returns cloud wallet details + */ + async deleteCloudWallet(cloudWalletDetails: IDeleteCloudWallet): Promise { + try { + const { userId } = cloudWalletDetails; + + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_DELETE_BY_TENANT_ID}${tenantId}`; + + await this.commonService.httpDelete(url, { headers: { authorization: decryptedApiKey } }); + + const res = await this.cloudWalletRepository.deleteCloudSubWallet(userId); + + return res; + } catch (error) { + this.logger.error(`[createCloudWallet] - error in create cloud wallet: ${error}`); + await this.commonService.handleError(error); + } + } + + + /** + * Create clous wallet + * @param cloudWalletDetails + * @returns cloud wallet details + */ + async getBaseWalletDetails(user:user): Promise { + try { + const baseWalletDetails = await this.cloudWalletRepository.getBaseWalletDetails(CloudWalletType.BASE_WALLET, user); + return baseWalletDetails; + } catch (error) { + this.logger.error(`[createCloudWallet] - error in create cloud wallet: ${error}`); + await this.commonService.handleError(error); + } + } + + async updateBaseWalletDetails(updateBaseWalletDto:UpdateBaseWalletDto): Promise { + try { + const baseWalletDetails = await this.cloudWalletRepository.updateBaseWalletDetails(CloudWalletType.BASE_WALLET, updateBaseWalletDto); + return baseWalletDetails; + } catch (error) { + this.logger.error(`[createCloudWallet] - error in create cloud wallet: ${error}`); + await this.commonService.handleError(error); + } + } + /** * Receive invitation * @param ReceiveInvitationDetails @@ -302,15 +512,17 @@ export class CloudWalletService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { email, userId, ...invitationDetails } = ReceiveInvitationDetails; - const checkUserExist = await this.cloudWalletRepository.checkUserExist(email); + const checkUserExist = await this.cloudWalletRepository.checkUserExist(userId); if (!checkUserExist) { throw new ConflictException(ResponseMessages.cloudWallet.error.walletNotExist); } - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; - const url = `${agentEndpoint}${CommonConstants.RECEIVE_INVITATION_BY_URL}`; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + const url = `${agentEndpoint}${CommonConstants.RECEIVE_INVITATION_BY_URL}${tenantId}`; const checkCloudWalletAgentHealth = await this.commonService.checkAgentHealth(agentEndpoint, decryptedApiKey); @@ -345,15 +557,17 @@ export class CloudWalletService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { email, userId, ...offerDetails } = acceptOfferDetails; - const checkUserExist = await this.cloudWalletRepository.checkUserExist(email); + const checkUserExist = await this.cloudWalletRepository.checkUserExist(userId); if (!checkUserExist) { throw new ConflictException(ResponseMessages.cloudWallet.error.walletNotExist); } - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const url = `${agentEndpoint}${CommonConstants.ACCEPT_OFFER}`; + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.ACCEPT_OFFER}${tenantId}`; const checkCloudWalletAgentHealth = await this.commonService.checkAgentHealth(agentEndpoint, decryptedApiKey); @@ -388,15 +602,17 @@ export class CloudWalletService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { email, userId, ...didDetails } = createDidDetails; - const checkUserExist = await this.cloudWalletRepository.checkUserExist(email); + const checkUserExist = await this.cloudWalletRepository.checkUserExist(userId); if (!checkUserExist) { throw new ConflictException(ResponseMessages.cloudWallet.error.walletNotExist); } - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; - const url = `${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`; + const url = `${agentEndpoint}${CommonConstants.URL_SHAGENT_CREATE_DID}${tenantId}`; const checkCloudWalletAgentHealth = await this.commonService.checkAgentHealth(agentEndpoint, decryptedApiKey); @@ -421,21 +637,67 @@ export class CloudWalletService { } } + /** + * Create DID for cloud wallet + * @param exportWallet + * @returns DID details + */ + async exportCloudWallet(exportWallet: IExportCloudWallet): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { email, userId, passKey, walletID } = exportWallet; + + const checkUserExist = await this.cloudWalletRepository.checkUserExist(userId); + + if (!checkUserExist) { + throw new ConflictException(ResponseMessages.cloudWallet.error.walletNotExist); + } + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + // Change here + const url = `${agentEndpoint}${CommonConstants.URL_CLOUD_WALLET_EXPORT}${tenantId}`; + + const checkCloudWalletAgentHealth = await this.commonService.checkAgentHealth(agentEndpoint, decryptedApiKey); + + if (!checkCloudWalletAgentHealth) { + throw new NotFoundException(ResponseMessages.cloudWallet.error.agentNotRunning); + } + const exportWalletResponse = await this.commonService.httpPost(url, {passKey, walletID}, { + headers: { authorization: decryptedApiKey } + }); + + if (!exportWalletResponse) { + throw new InternalServerErrorException(ResponseMessages.cloudWallet.error.exportWallet, { + cause: new Error(), + description: ResponseMessages.errorMessages.serverError + }); + } + + return exportWalletResponse; + } catch (error) { + this.logger.error(`[export wallet] - error while exporting wallet: ${error}`); + await this.commonService.handleError(error); + } + } + /** * Get DID list by tenant id * @param walletDetails * @returns DID list */ - async getDidList(walletDetails: IWalletDetailsForDidList): Promise { + async getDidList(walletDetails: IWalletDetailsForDidList): Promise { try { const { userId } = walletDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const url = `${agentEndpoint}${CommonConstants.URL_AGENT_GET_DID}`; + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_DID_LIST}${tenantId}?isDefault=${walletDetails.isDefault}`; - const didList = (await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } })) ?? []; + const didList = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + didList['hashTenantID'] = md5(tenantId); return didList; } catch (error) { await this.commonService.handleError(error); @@ -451,14 +713,14 @@ export class CloudWalletService { async getconnectionById(connectionDetails: IConnectionDetailsById): Promise { try { const { userId, connectionId } = connectionDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CONNECTION_BY_ID}/${connectionId}`; + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; - const connectionDetailResponse = await this.commonService.httpGet(url, { - headers: { authorization: decryptedApiKey } - }); + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CONNECTION_BY_ID}${connectionId}/${tenantId}`; + + const connectionDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return connectionDetailResponse; } catch (error) { await this.commonService.handleError(error); @@ -466,37 +728,62 @@ export class CloudWalletService { } } - /** - * Get connection list by tenant id + + /** + * Add connection Type * @param connectionDetails * @returns Connection Details */ - async getAllconnectionById(connectionDetails: GetAllCloudWalletConnections): Promise { + async AddConnectionTypeById(connectionDetails: IAddConnectionType): Promise { try { - const { userId, alias, myDid, outOfBandId, theirDid, theirLabel } = connectionDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const urlOptions = { - alias, - myDid, - outOfBandId, - theirDid, - theirLabel - }; - const optionalParameter = await this.commonService.createDynamicUrl(urlOptions); - const { agentEndpoint } = baseWalletDetails; + const { userId, connectionId, connectionType} = connectionDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CONNECTION_BY_ID}${optionalParameter}`; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_ADD_CONNECTION_TYPE}${connectionId}/${tenantId}`; - const connectionDetailList = await this.commonService.httpGet(url, { + const connectionDetailResponse = await this.commonService.httpPost(url, {connectionType}, { headers: { authorization: decryptedApiKey } }); - return connectionDetailList; + return connectionDetailResponse; } catch (error) { await this.commonService.handleError(error); throw error; } } + /** + * Get connection list by tenant id + * @param connectionDetails + * @returns Connection Details + */ + async getAllconnectionById(connectionDetails: GetAllCloudWalletConnections): Promise { + try { + const { userId, alias, myDid, outOfBandId, theirDid, theirLabel } = connectionDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + const urlOptions = { + alias, + myDid, + outOfBandId, + theirDid, + theirLabel + }; + const optionalParameter = await this.commonService.createDynamicUrl(urlOptions); + const { tenantId } = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CONNECTION_BY_ID}${tenantId}${optionalParameter}`; + + const connectionDetailList = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return connectionDetailList; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + /** * Get credential list by tenant id * @param tenantDetails @@ -505,21 +792,20 @@ export class CloudWalletService { async getCredentialListById(tenantDetails: ITenantDetail): Promise { try { const { userId, connectionId, state, threadId } = tenantDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); const urlOptions = { connectionId, state, threadId }; - const optionalParameter = await this.commonService.createDynamicUrl(urlOptions); + const {tenantId} = getTenant; + const optionalParameter = await this.commonService.createDynamicUrl(urlOptions); + + const { agentEndpoint } = getTenant; - const { agentEndpoint } = baseWalletDetails; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREDENTIAL}/${tenantId}${optionalParameter}`; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREDENTIAL}${optionalParameter}`; - - const credentialDetailResponse = await this.commonService.httpGet(url, { - headers: { authorization: decryptedApiKey } - }); + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return credentialDetailResponse; } catch (error) { await this.commonService.handleError(error); @@ -530,18 +816,19 @@ export class CloudWalletService { /** * Get credential by record id * @param credentialDetails - * @returns Connection Details + * @returns credential Details */ async getCredentialByRecord(credentialDetails: ICredentialDetails): Promise { try { const { userId, credentialRecordId } = credentialDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREDENTIAL}/${credentialRecordId}`; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; - const credentialDetailResponse = await this.commonService.httpGet(url, { - headers: { authorization: decryptedApiKey } - }); + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREDENTIAL}/${credentialRecordId}/${tenantId}`; + + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return credentialDetailResponse; } catch (error) { await this.commonService.handleError(error); @@ -549,6 +836,99 @@ export class CloudWalletService { } } + /** + * Get credential by record id + * @param credentialDetails + * @returns credential Details + */ + async getCredentialFormatDataByRecord(credentialDetails: ICredentialDetails): Promise { + try { + const { userId, credentialRecordId } = credentialDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_CREDENTIAL_FORMAT_DATA}/${credentialRecordId}/${tenantId}`; + + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + + /** + * Get credential by record id + * @param proofDetails + * @returns credential Details + */ + async getProofFormDataByRecord(proofDetails: IProofPresentationDetails): Promise { + try { + const { userId, proofRecordId } = proofDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_PROOF_FORM_DATA}/${tenantId}/${proofRecordId}`; + + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + + + /** + * Get credential by record id + * @param credentialDetails + * @returns credential Details + */ + async deleteCredentialByRecord(credentialDetails: ICredentialDetails): Promise { + try { + const { userId, credentialRecordId } = credentialDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_DELETE_CREDENTIAL}/${credentialRecordId}/${tenantId}`; + + const credentialDetailResponse = await this.commonService.httpDelete(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + + + /** + * Delete W3C credential by record id + * @param credentialDetails + */ + async deleteW3cCredentialByRecord(credentialDetails: ICredentialDetails): Promise { + try { + const { userId, credentialRecordId } = credentialDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_DELETE_W3C_CREDENTIAL}/${credentialRecordId}/${tenantId}`; + + const credentialDetailResponse = await this.commonService.httpDelete(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + /** * Get basic-message by connection id * @param connectionDetails @@ -557,14 +937,14 @@ export class CloudWalletService { async getBasicMessageByConnectionId(connectionDetails: IBasicMessage): Promise { try { const { userId, connectionId } = connectionDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_BASIC_MESSAGE}${connectionId}`; + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_BASIC_MESSAGE}${connectionId}/${tenantId}`; - const basicMessageResponse = await this.commonService.httpGet(url, { - headers: { authorization: decryptedApiKey } - }); + const basicMessageResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); return basicMessageResponse; } catch (error) { await this.commonService.handleError(error); @@ -572,29 +952,115 @@ export class CloudWalletService { } } - /** + /** * Send basic-message by connection id * @param messageDetails * @returns Basic message Details */ - async sendBasicMessage(messageDetails: IBasicMessageDetails): Promise { + async sendBasicMessage(messageDetails: IBasicMessageDetails): Promise { try { const { userId, connectionId, content } = messageDetails; - const [baseWalletDetails, decryptedApiKey] = await this._commonCloudWalletInfo(userId); - const { agentEndpoint } = baseWalletDetails; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; - const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_BASIC_MESSAGE}${connectionId}`; - const basicMessageResponse = await this.commonService.httpPost( - url, - { content }, - { - headers: { authorization: decryptedApiKey } - } - ); + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_BASIC_MESSAGE}${connectionId}/${tenantId}`; + const basicMessageResponse = await this.commonService.httpPost(url, {content}, { + headers: { authorization: decryptedApiKey } + }); return basicMessageResponse; } catch (error) { await this.commonService.handleError(error); throw error; } + } + + /** + * Create self-attested W3C credential + * @param selfAttestedCredential + * @returns Self-attested credential Details + */ + async createSelfAttestedW3cCredential(selfAttestedCredential: ISelfAttestedCredential): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { email, userId, ...selfAttestedDetails } = selfAttestedCredential; + + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_SELF_ATTESTED_W3C_CREDENTIAL}${tenantId}`; + + const checkCloudWalletAgentHealth = await this.commonService.checkAgentHealth(agentEndpoint, decryptedApiKey); + + if (!checkCloudWalletAgentHealth) { + throw new NotFoundException(ResponseMessages.cloudWallet.error.agentNotRunning); + } + const selfAttestedCredentialResponse = await this.commonService.httpPost(url, selfAttestedDetails, { + headers: { authorization: decryptedApiKey } + }); + + if (!selfAttestedCredentialResponse) { + throw new InternalServerErrorException(ResponseMessages.cloudWallet.error.createSelfAttestedW3cCredential, { + cause: new Error(), + description: ResponseMessages.errorMessages.serverError + }); + } + + selfAttestedCredentialResponse.tenantId = tenantId; + + return selfAttestedCredentialResponse; + } catch (error) { + this.logger.error(`[createSelfAttestedW3cCredential] - error in create self-attested credential: ${error}`); + await this.commonService.handleError(error); + } + } + + /** + * Get all W3C credential by tenant id + * @param w3cCredential + * @returns W3C Credential list + */ + async getAllW3cCredentials(w3cCredential: IW3cCredentials): Promise { + try { + const { userId } = w3cCredential; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_W3C_CREDENTIAL}${tenantId}`; + + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } + } + + /** + * Get W3C credential by record id + * @param w3cCredential + * @returns W3C credential Details + */ + async getW3cCredentialByRecordId(w3cCredential: IW3cCredentials): Promise { + try { + const { userId, credentialRecordId } = w3cCredential; + const [getTenant, decryptedApiKey] = await this._commonCloudWalletInfo(userId); + + const {tenantId} = getTenant; + const { agentEndpoint } = getTenant; + + const url = `${agentEndpoint}${CommonConstants.CLOUD_WALLET_W3C_CREDENTIAL}${tenantId}/${credentialRecordId}`; + + const credentialDetailResponse = await this.commonService.httpGet(url, { headers: { authorization: decryptedApiKey } }); + return credentialDetailResponse; + } catch (error) { + await this.commonService.handleError(error); + throw error; + } } } diff --git a/apps/cloud-wallet/src/main.ts b/apps/cloud-wallet/src/main.ts index c6abbdb1c..2168ad546 100644 --- a/apps/cloud-wallet/src/main.ts +++ b/apps/cloud-wallet/src/main.ts @@ -5,7 +5,6 @@ import { Logger } from '@nestjs/common'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -15,7 +14,7 @@ async function bootstrap(): Promise { transport: Transport.NATS, options: getNatsOptions(CommonConstants.CLOUD_WALLET_SERVICE, process.env.CLOUD_WALLET_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/connection/src/connection.controller.ts b/apps/connection/src/connection.controller.ts index 0cc2e63d7..be3e3ccd8 100644 --- a/apps/connection/src/connection.controller.ts +++ b/apps/connection/src/connection.controller.ts @@ -11,9 +11,10 @@ import { IReceiveInvitationByUrlOrg, IReceiveInvitationResponse } from './interfaces/connection.interfaces'; -import { IConnectionList, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; +import { IConnectionList, IDeletedConnectionsRecord, orgAgents } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IQuestionPayload } from './interfaces/messaging.interfaces'; +// eslint-disable-next-line camelcase import { user } from '@prisma/client'; @Controller() export class ConnectionController { @@ -25,7 +26,8 @@ export class ConnectionController { * @returns Callback URL for connection and created connections details */ @MessagePattern({ cmd: 'webhook-get-connection' }) - async getConnectionWebhook(payload: ICreateConnection): Promise { + // eslint-disable-next-line camelcase + async getConnectionWebhook(payload: ICreateConnection): Promise { return this.connectionService.getConnectionWebhook(payload); } diff --git a/apps/connection/src/connection.module.ts b/apps/connection/src/connection.module.ts index 4823f6dfd..bd711711c 100644 --- a/apps/connection/src/connection.module.ts +++ b/apps/connection/src/connection.module.ts @@ -11,11 +11,6 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { CommonConstants } from '@credebl/common/common.constant'; // import { nkeyAuthenticator } from 'nats'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -28,11 +23,9 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, CacheModule.register() ], controllers: [ConnectionController], - providers: [ConnectionService, ConnectionRepository, UserActivityRepository, PrismaService, Logger, NATSClient] + providers: [ConnectionService, ConnectionRepository, UserActivityRepository, PrismaService, Logger] }) export class ConnectionModule { } diff --git a/apps/connection/src/connection.repository.ts b/apps/connection/src/connection.repository.ts index ec43c8385..5fe4b2bc2 100644 --- a/apps/connection/src/connection.repository.ts +++ b/apps/connection/src/connection.repository.ts @@ -114,15 +114,23 @@ export class ConnectionRepository { * @returns Get connection details */ // eslint-disable-next-line camelcase - async saveConnectionWebhook(payload: ICreateConnection): Promise { + async saveConnectionWebhook(payload: ICreateConnection): Promise { try { + // eslint-disable-next-line camelcase + let agentOrg: org_agents; let organisationId; const { connectionDto, orgId } = payload; if ('default' !== connectionDto?.contextCorrelationId) { - const getOrganizationId = await this.getOrganization(connectionDto?.contextCorrelationId); - organisationId = getOrganizationId?.orgId; + agentOrg = await this.getOrganizationByTenantId(connectionDto?.contextCorrelationId); + if (agentOrg?.orgId) { + organisationId = agentOrg?.orgId; + } else { + agentOrg = await this.getOrganizationByOrgId(orgId); + organisationId = orgId; + } } else { + agentOrg = await this.getOrganizationByOrgId(orgId); organisationId = orgId; } @@ -165,7 +173,7 @@ export class ConnectionRepository { break; } - return this.prisma.connections.upsert({ + await this.prisma.connections.upsert({ where: { connectionId: connectionDto?.id }, @@ -185,6 +193,7 @@ export class ConnectionRepository { orgId: organisationId } }); + return agentOrg; } catch (error) { this.logger.error(`Error in saveConnectionWebhook: ${error.message} `); throw error; @@ -213,7 +222,7 @@ export class ConnectionRepository { } // eslint-disable-next-line camelcase - async getOrganization(tenantId: string): Promise { + async getOrganizationByTenantId(tenantId: string): Promise { try { return this.prisma.org_agents.findFirst({ where: { @@ -226,6 +235,20 @@ export class ConnectionRepository { } } + // eslint-disable-next-line camelcase + async getOrganizationByOrgId(orgId: string): Promise { + try { + return this.prisma.org_agents.findFirst({ + where: { + orgId + } + }); + } catch (error) { + this.logger.error(`Error in getOrganization in issuance repository: ${error.message} `); + throw error; + } + } + /** * Description: Fetch ShorteningUrl details * @param referenceId @@ -379,11 +402,20 @@ export class ConnectionRepository { } // eslint-disable-next-line camelcase - async getInvitationDidByOrgId(orgId: string): Promise { + async getInvitationDidByOrgId(orgId: string): Promise { try { - return this.prisma.agent_invitations.findMany({ + return this.prisma.agent_invitations.findFirst({ where: { - orgId + AND: [ + { + orgId + }, + { + invitationDid: { + not: null + } + } + ] }, orderBy: { createDateTime: 'asc' diff --git a/apps/connection/src/connection.service.ts b/apps/connection/src/connection.service.ts index 8442c7421..ca4cee4c6 100644 --- a/apps/connection/src/connection.service.ts +++ b/apps/connection/src/connection.service.ts @@ -1,9 +1,9 @@ /* eslint-disable camelcase */ import { CommonService } from '@credebl/common'; import { CommonConstants } from '@credebl/common/common.constant'; -import { HttpException, HttpStatus, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { HttpException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; -import { from, map } from 'rxjs'; +import { map } from 'rxjs'; import { ConnectionResponseDetail, AgentConnectionSearchCriteria, @@ -18,21 +18,15 @@ import { import { ConnectionRepository } from './connection.repository'; import { ResponseMessages } from '@credebl/common/response-messages'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { ConnectionProcessState } from '@credebl/enum/enum'; +import { OrgAgentType, ConnectionProcessState } from '@credebl/enum/enum'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { - IConnectionList, - ICreateConnectionUrl, - IDeletedConnectionsRecord -} from '@credebl/common/interfaces/connection.interface'; +import { IConnectionList, ICreateConnectionUrl, IDeletedConnectionsRecord } from '@credebl/common/interfaces/connection.interface'; import { IConnectionDetailsById } from 'apps/api-gateway/src/interfaces/IConnectionSearch.interface'; import { IBasicMessage, IQuestionPayload } from './interfaces/messaging.interfaces'; -import { RecordType, user } from '@prisma/client'; +import { org_agents, RecordType, user } from '@prisma/client'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { agent_invitations } from '@prisma/client'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { getAgentUrl } from '@credebl/common/common.utils'; @Injectable() export class ConnectionService { constructor( @@ -41,9 +35,7 @@ export class ConnectionService { private readonly connectionRepository: ConnectionRepository, private readonly userActivityRepository: UserActivityRepository, private readonly logger: Logger, - // TODO: Remove unused variable - @Inject(CACHE_MANAGER) private readonly cacheService: Cache, - private readonly natsClient: NATSClient + @Inject(CACHE_MANAGER) private cacheService: Cache ) {} /** @@ -51,10 +43,10 @@ export class ConnectionService { * @param orgId * @returns Callback URL for connection and created connections details */ - async getConnectionWebhook(payload: ICreateConnection): Promise { + async getConnectionWebhook(payload: ICreateConnection): Promise { try { - const saveConnectionDetails = await this.connectionRepository.saveConnectionWebhook(payload); - return saveConnectionDetails; + const orgAgent = await this.connectionRepository.saveConnectionWebhook(payload); + return orgAgent; } catch (error) { this.logger.error(`[getConnectionWebhook] - error in fetch connection webhook: ${error}`); throw new RpcException(error.response ? error.response : error); @@ -104,6 +96,7 @@ export class ConnectionService { try { return await this.connectionRepository.getConnectionRecordsCount(orgId); } catch (error) { + this.logger.error( `[getConnectionRecords ] [NATS call]- error in get connection records count : ${JSON.stringify(error)}` ); @@ -189,13 +182,23 @@ export class ConnectionService { try { const { alias, myDid, outOfBandId, state, theirDid, theirLabel } = connectionSearchCriteria; const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } let url: string; - url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTIONS}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATIONS}`.replace( + '#', + agentDetails.tenantId + ); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } //Create the dynamic URL for Search Criteria const criteriaParams = []; @@ -254,11 +257,24 @@ export class ConnectionService { async getConnectionsById(user: IUserRequest, connectionId: string, orgId: string): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTION_BY_ID}`.replace('#', connectionId); + + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_GET_CONNECTION_BY_ID}`.replace('#', connectionId); + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREATEED_INVITATION_BY_CONNECTIONID}` + .replace('#', connectionId) + .replace('@', agentDetails.tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + const createConnectionInvitation = await this._getConnectionsByConnectionId(url, orgId); return createConnectionInvitation?.response; } catch (error) { @@ -279,11 +295,14 @@ export class ConnectionService { async getQuestionAnswersRecord(orgId: string): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = await getAgentUrl(agentEndPoint, CommonConstants.GET_QUESTION_ANSWER_RECORD); + + const label = 'get-question-answer-record'; + const url = await this.getQuestionAnswerAgentUrl(label, orgAgentType, agentEndPoint, agentDetails?.tenantId); const record = await this._getQuestionAnswersRecord(url, orgId); return record; @@ -311,6 +330,91 @@ export class ConnectionService { return this.natsCall(pattern, payload); } + /** + * Description: Fetch agent url + * @param referenceId + * @returns agent URL + */ + async getAgentUrl( + orgAgentType: string, + agentEndPoint: string, + tenantId?: string, + connectionInvitationFlag?: string + ): Promise { + try { + let url; + if ('connection-invitation' === connectionInvitationFlag) { + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_INVITE}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_CONNECTION_INVITATION}`.replace('#', tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + } else { + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_CONN_LEGACY_INVITE}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_INVITATION}`.replace('#', tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + } + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } + } + + async getQuestionAnswerAgentUrl( + label: string, + orgAgentType: string, + agentEndPoint: string, + tenantId?: string, + connectionId?: string + ): Promise { + try { + let url; + switch (label) { + case 'send-question': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_SEND_QUESTION}`.replace('#', connectionId) + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_SEND_QUESTION}` + .replace('#', connectionId) + .replace('@', tenantId) + : null; + break; + } + + case 'get-question-answer-record': { + url = + orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_QUESTION_ANSWER_RECORD}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_QUESTION_ANSWER_RECORD}`.replace('#', tenantId) + : null; + break; + } + + default: { + break; + } + } + + if (!url) { + throw new NotFoundException(ResponseMessages.issuance.error.agentUrlNotFound); + } + + return url; + } catch (error) { + this.logger.error(`Error get question answer agent Url: ${JSON.stringify(error)}`); + throw error; + } + } + async _getOrgAgentApiKey(orgId: string): Promise<{ response: string; }> { @@ -338,24 +442,31 @@ export class ConnectionService { ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION_URL}`; + + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION_URL}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION_URL}`.replace( + '#', + agentDetails.tenantId + ); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + const createConnectionInvitation = await this._receiveInvitationUrl(url, orgId, receiveInvitationUrl); return createConnectionInvitation.response; } catch (error) { - this.logger.error(`[receiveInvitationUrl] - error in receive invitation url : ${JSON.stringify(error, null, 2)}`); + this.logger.error(`[receiveInvitationUrl] - error in receive invitation url : ${JSON.stringify(error)}`); - const customErrorMessage = error?.status?.message?.error?.message; - if (customErrorMessage) { - throw new RpcException({ - statusCode: HttpStatus.CONFLICT, - message: customErrorMessage, - error: ResponseMessages.errorMessages.conflict - }); - } else if (error?.response?.error?.reason) { + if (error?.response?.error?.reason) { throw new RpcException({ message: ResponseMessages.connection.error.connectionNotFound, statusCode: error?.response?.status, @@ -376,19 +487,7 @@ export class ConnectionService { }> { const pattern = { cmd: 'agent-receive-invitation-url' }; const payload = { url, orgId, receiveInvitationUrl }; - - try { - return await this.natsCall(pattern, payload); - } catch (error) { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - } + return this.natsCall(pattern, payload); } async receiveInvitation( @@ -398,11 +497,22 @@ export class ConnectionService { ): Promise { try { const agentDetails = await this.connectionRepository.getAgentEndPoint(orgId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const { agentEndPoint } = agentDetails; if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION}`; + + let url; + if (orgAgentType === OrgAgentType.DEDICATED) { + url = `${agentEndPoint}${CommonConstants.URL_RECEIVE_INVITATION}`; + } else if (orgAgentType === OrgAgentType.SHARED) { + url = `${agentEndPoint}${CommonConstants.URL_SHAGENT_RECEIVE_INVITATION}`.replace('#', agentDetails.tenantId); + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + const createConnectionInvitation = await this._receiveInvitation(url, orgId, receiveInvitation); return createConnectionInvitation?.response; } catch (error) { @@ -455,19 +565,30 @@ export class ConnectionService { question }; - const url = await getAgentUrl(agentEndPoint, CommonConstants.SEND_QUESTION, connectionId); + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const label = 'send-question'; + const url = await this.getQuestionAnswerAgentUrl( + label, + orgAgentType, + agentEndPoint, + agentDetails?.tenantId, + connectionId + ); const createQuestion = await this._sendQuestion(questionPayload, url, orgId); return createQuestion; } catch (error) { this.logger.error(`[sendQuestion] - error in sending question: ${error}`); - if (error?.status?.message?.error) { + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR + message: error?.status?.message?.error?.reason + ? error?.status?.message?.error?.reason + : error?.status?.message?.error, + statusCode: error?.status?.code }); + } else { + throw new RpcException(error.response ? error.response : error); } - throw new RpcException(error.response || error); } } @@ -500,6 +621,7 @@ export class ConnectionService { */ async createConnectionInvitation(payload: ICreateOutOfbandConnectionInvitation): Promise { try { + const { alias, appendedAttachments, @@ -528,21 +650,16 @@ export class ConnectionService { throw new NotFoundException(ResponseMessages.connection.error.agentEndPointNotFound); } - let legacyinvitationDid; + let legacyInvitationDid; if (IsReuseConnection) { - const data: agent_invitations[] = await this.connectionRepository.getInvitationDidByOrgId(orgId); - if (data && 0 < data.length) { - const [firstElement] = data; - legacyinvitationDid = firstElement?.invitationDid ?? undefined; - - this.logger.log('legacyinvitationDid:', legacyinvitationDid); - } + const invitation: agent_invitations = await this.connectionRepository.getInvitationDidByOrgId(orgId); + legacyInvitationDid = invitation?.invitationDid ?? undefined; + this.logger.debug('legacyInvitationDid:', legacyInvitationDid); } - const connectionInvitationDid = invitationDid ? invitationDid : legacyinvitationDid; + const connectionInvitationDid = invitationDid ? invitationDid : legacyInvitationDid; this.logger.log('connectionInvitationDid:', connectionInvitationDid); - this.logger.log(`logoUrl:::, ${organisation.logoUrl}`); const connectionPayload = { multiUseInvitation: multiUseInvitation ?? true, autoAcceptConnection: autoAcceptConnection ?? true, @@ -559,7 +676,15 @@ export class ConnectionService { recipientKey: recipientKey || undefined, invitationDid: connectionInvitationDid || undefined }; - const url = await getAgentUrl(agentEndPoint, CommonConstants.CONNECTION_INVITATION); + + const createConnectionInvitationFlag = 'connection-invitation'; + const orgAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const url = await this.getAgentUrl( + orgAgentType, + agentEndPoint, + agentDetails?.tenantId, + createConnectionInvitationFlag + ); const createConnectionInvitation = await this._createOutOfBandConnectionInvitation(connectionPayload, url, orgId); const connectionInvitationUrl = createConnectionInvitation?.response?.invitationUrl; const shortenedUrl = await this.storeConnectionObjectAndReturnUrl( @@ -572,7 +697,7 @@ export class ConnectionService { shortenedUrl, agentId, orgId, - invitationsDid + invitationsDid ); const connectionStorePayload: ConnectionResponseDetail = { id: saveConnectionDetails.id, @@ -631,7 +756,8 @@ export class ConnectionService { response: string; }> { try { - return from(this.natsClient.send(this.connectionServiceProxy, pattern, payload)) + return this.connectionServiceProxy + .send(pattern, payload) .pipe( map((response) => ({ response @@ -655,58 +781,63 @@ export class ConnectionService { } handleError(error): Promise { - if (error?.status?.message?.error) { + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR + message: error?.status?.message?.error?.reason + ? error?.status?.message?.error?.reason + : error?.status?.message?.error, + statusCode: error?.status?.code }); + } else { + throw new RpcException(error.response ? error.response : error); } - throw new RpcException(error.response || error); } async deleteConnectionRecords(orgId: string, user: user): Promise { try { - const deleteConnections = await this.connectionRepository.deleteConnectionRecordsByOrgId(orgId); + const deleteConnections = await this.connectionRepository.deleteConnectionRecordsByOrgId(orgId); - if (0 === deleteConnections?.deleteConnectionRecords?.count) { - throw new NotFoundException(ResponseMessages.connection.error.connectionRecordNotFound); - } + if (0 === deleteConnections?.deleteConnectionRecords?.count) { + throw new NotFoundException(ResponseMessages.connection.error.connectionRecordNotFound); + } - const statusCounts = { - [ConnectionProcessState.START]: 0, - [ConnectionProcessState.COMPLETE]: 0, - [ConnectionProcessState.ABANDONED]: 0, - [ConnectionProcessState.INVITATION_SENT]: 0, - [ConnectionProcessState.INVITATION_RECEIVED]: 0, - [ConnectionProcessState.REQUEST_SENT]: 0, - [ConnectionProcessState.DECLIEND]: 0, - [ConnectionProcessState.REQUEST_RECEIVED]: 0, - [ConnectionProcessState.RESPONSE_SENT]: 0, - [ConnectionProcessState.RESPONSE_RECEIVED]: 0 - }; + const statusCounts = { + [ConnectionProcessState.START]: 0, + [ConnectionProcessState.COMPLETE]: 0, + [ConnectionProcessState.ABANDONED]: 0, + [ConnectionProcessState.INVITATION_SENT]: 0, + [ConnectionProcessState.INVITATION_RECEIVED]: 0, + [ConnectionProcessState.REQUEST_SENT]: 0, + [ConnectionProcessState.DECLIEND]: 0, + [ConnectionProcessState.REQUEST_RECEIVED]: 0, + [ConnectionProcessState.RESPONSE_SENT]: 0, + [ConnectionProcessState.RESPONSE_RECEIVED]: 0 + }; - await Promise.all( - deleteConnections.getConnectionRecords.map(async (record) => { - statusCounts[record.state]++; - }) - ); + await Promise.all(deleteConnections.getConnectionRecords.map(async (record) => { + statusCounts[record.state]++; + })); - const filteredStatusCounts = Object.fromEntries(Object.entries(statusCounts).filter((entry) => 0 < entry[1])); + const filteredStatusCounts = Object.fromEntries( + Object.entries(statusCounts).filter(entry => 0 < entry[1]) + ); - const deletedConnectionData = { - deletedConnectionsRecordsCount: deleteConnections?.deleteConnectionRecords?.count, - deletedRecordsStatusCount: filteredStatusCounts - }; + const deletedConnectionData = { + deletedConnectionsRecordsCount: deleteConnections?.deleteConnectionRecords?.count, + deletedRecordsStatusCount: filteredStatusCounts + }; + + await this.userActivityRepository._orgDeletedActivity(orgId, user, deletedConnectionData, RecordType.CONNECTION); - await this.userActivityRepository._orgDeletedActivity(orgId, user, deletedConnectionData, RecordType.CONNECTION); + return deleteConnections; - return deleteConnections; } catch (error) { - this.logger.error(`[deleteConnectionRecords] - error in deleting connection records: ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`[deleteConnectionRecords] - error in deleting connection records: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); } } + async sendBasicMesage(payload: IBasicMessage): Promise { const { content, orgId, connectionId } = payload; try { @@ -721,19 +852,31 @@ export class ConnectionService { const questionPayload = { content }; - const agentUrl = await getAgentUrl(agentEndPoint, CommonConstants.SEND_BASIC_MESSAGE, connectionId); + + const organizationAgentType = await this.connectionRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const label = 'send-basic-message'; + const agentUrl = await this.commonService.sendBasicMessageAgentUrl( + label, + organizationAgentType, + agentEndPoint, + agentDetails?.tenantId, + connectionId + ); const sendBasicMessage = await this._sendBasicMessageToAgent(questionPayload, agentUrl, orgId); return sendBasicMessage; } catch (error) { this.logger.error(`[sendBasicMesage] - error in send basic message: ${error}`); - if (error?.status?.message?.error) { + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR + message: error?.status?.message?.error?.reason + ? error?.status?.message?.error?.reason + : error?.status?.message?.error, + statusCode: error?.status?.code }); + } else { + throw new RpcException(error.response ? error.response : error); } - throw new RpcException(error.response || error); } } @@ -743,4 +886,5 @@ export class ConnectionService { // eslint-disable-next-line no-return-await return await this.natsCall(pattern, payload); } + } diff --git a/apps/connection/src/interfaces/connection.interfaces.ts b/apps/connection/src/interfaces/connection.interfaces.ts index 020e4821d..99ea31b24 100644 --- a/apps/connection/src/interfaces/connection.interfaces.ts +++ b/apps/connection/src/interfaces/connection.interfaces.ts @@ -210,8 +210,8 @@ interface OutOfBandInvitationService { } interface OutOfBandInvitation { - "@type": string; - "@id": string; + '@type': string; + '@id': string; label: string; accept: string[]; handshake_protocols: string[]; diff --git a/apps/connection/src/main.ts b/apps/connection/src/main.ts index 752b048f6..22c572fd3 100644 --- a/apps/connection/src/main.ts +++ b/apps/connection/src/main.ts @@ -5,7 +5,6 @@ import { Logger } from '@nestjs/common'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -15,8 +14,7 @@ async function bootstrap(): Promise { transport: Transport.NATS, options: getNatsOptions(CommonConstants.CONNECTION_SERVICE, process.env.CONNECTION_NKEY_SEED) }); - - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts new file mode 100644 index 000000000..99bcc61c5 --- /dev/null +++ b/apps/ecosystem/dtos/accept-reject-ecosysteminvitation.dto.ts @@ -0,0 +1,11 @@ +import { Invitation } from '@credebl/enum/enum'; + +export class AcceptRejectEcosystemInvitationDto { + orgId: string; + invitationId: string; + status: Invitation; + orgName?: string; + orgDid?: string; + userId?: string; + userEmail?: string; +} diff --git a/apps/ecosystem/dtos/send-invitation.dto.ts b/apps/ecosystem/dtos/send-invitation.dto.ts new file mode 100644 index 000000000..476415ecd --- /dev/null +++ b/apps/ecosystem/dtos/send-invitation.dto.ts @@ -0,0 +1,12 @@ +import { ApiExtraModels } from '@nestjs/swagger'; + +@ApiExtraModels() +export class SendInvitationDto { + email: string; +} + +@ApiExtraModels() +export class BulkSendInvitationDto { + invitations: SendInvitationDto[]; + ecosystemId: string; +} \ No newline at end of file diff --git a/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts b/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts new file mode 100644 index 000000000..0badcf828 --- /dev/null +++ b/apps/ecosystem/dtos/update-ecosystem-invitation.dto.ts @@ -0,0 +1,8 @@ +export class updateEcosystemInvitationDto { + invitationId: string; + orgId: string; + status: string; + userId: string; + email: string; + roleId: string; +} \ No newline at end of file diff --git a/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts new file mode 100644 index 000000000..c1d431a77 --- /dev/null +++ b/apps/ecosystem/dtos/update-ecosystemOrgs.dto.ts @@ -0,0 +1,8 @@ +export class updateEcosystemOrgsDto { + orgId: string; + status: string; + ecosystemId: string; + ecosystemRoleId: string; + createdBy?: string; + lastChangedBy?: string; +} \ No newline at end of file diff --git a/apps/ecosystem/enums/ecosystem.enum.ts b/apps/ecosystem/enums/ecosystem.enum.ts new file mode 100644 index 000000000..e0011aef5 --- /dev/null +++ b/apps/ecosystem/enums/ecosystem.enum.ts @@ -0,0 +1,36 @@ +export enum EcosystemRoles { + ECOSYSTEM_LEAD = 'Ecosystem Lead', + ECOSYSTEM_MEMBER = 'Ecosystem Member', + ECOSYSTEM_OWNER = 'Ecosystem Owner' +} + +export enum EcosystemOrgStatus { + ACTIVE = 'ACTIVE' +} + +export enum EcosystemInvitationStatus { + ACCEPTED = 'accepted', + REJECTED = 'rejected', + PENDING = 'pending' +} + +export enum endorsementTransactionStatus { + REQUESTED = 'requested', + SIGNED = 'signed', + DECLINED = 'declined', + SUBMITED = 'submited' +} + +export enum endorsementTransactionType { + SCHEMA = 'schema', + W3C_SCHEMA = 'w3c Schema', + CREDENTIAL_DEFINITION = 'credential-definition', + SIGN = 'sign', + SUBMIT = 'submit' +} + +export enum DeploymentModeType { + PROVIDER_HOSTED = 'ProviderHosted', + ON_PREMISE = 'OnPremise' +} + diff --git a/apps/ecosystem/interfaces/ecosystem.interfaces.ts b/apps/ecosystem/interfaces/ecosystem.interfaces.ts new file mode 100644 index 000000000..4b23303a9 --- /dev/null +++ b/apps/ecosystem/interfaces/ecosystem.interfaces.ts @@ -0,0 +1,487 @@ +import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; +import { Prisma } from '@prisma/client'; +import { IUserRequestInterface } from 'apps/ledger/src/schema/interfaces/schema.interface'; +export interface AttributeValue { + attributeName: string; + schemaDataType: string; + displayName: string; +} + +export interface IW3CSchemaAttributesValue { + attributeName: string; + schemaDataType: W3CSchemaDataType; + displayName: string; + isRequired: boolean; +} +export interface IRequestSchemaEndorsement { + type: SchemaTypeEnum, + endorse?: boolean, + schemaPayload: IRequestIndySchemaEndorsement | IRequestW3CSchemaEndorsement +} + +export interface IRequestIndySchemaEndorsement { + schemaName: string; + schemaVersion: string; + attributes: AttributeValue[]; +} +export interface IRequestW3CSchemaEndorsement { + schemaName: string; + attributes: IW3CSchemaAttributesValue[]; + schemaType: JSONSchemaType; + description: string; +} + +export interface RequestCredDeffEndorsement { + schemaId: string; + tag: string; + endorse?: boolean; + schemaDetails?: object; + userId?: string; +} + +export interface IAttributeValue { + attributeName: string; + schemaDataType: string; + displayName: string; +} + +export interface SchemaTransactionPayload { + endorserDid: string; + endorse: boolean; + attributes: string[]; + version: string; + name: string; + issuerId: string; +} + +export interface CredDefTransactionPayload { + endorserDid: string; + endorse: boolean; + tag: string; + schemaId: string; + issuerId: string; +} + +export interface SchemaMessage { + message?: { + jobId: string; + schemaState: { + state: string; + action: string; + schemaId: string; + schema: Record; + schemaRequest: string; + }; + registrationMetadata: Record; + schemaMetadata: Record; + }; +} + +export interface CredDefMessage { + message?: { + jobId: string; + credentialDefinitionState: { + state: string; + action: string; + schemaId: string; + schema: Record; + credentialDefinitionRequest: string; + credentialDefinition: Record; + }; + registrationMetadata: Record; + schemaMetadata: Record; + }; +} +export interface SchemaTransactionResponse { + endorserDid: string; + authorDid: string; + requestPayload: string; + status: string; + ecosystemOrgId: string; + userId?: string; +} + +export interface SignedTransactionMessage { + message?: { + signedTransaction: string; + }; +} + +export interface IEndorsementTransaction { + id: string; + endorserDid: string; + authorDid: string; + status: string; + ecosystemOrgId: string; + createDateTime: Date; + createdBy: string; + type?: string; +} + +interface SchemaPayload { + attributes: string[]; + version: string; + name: string; + issuerId: string; +} + +interface CredentialDefinitionPayload { + tag: string; + issuerId: string; + schemaId: string; + type: string; + value: Record; +} + +export interface submitTransactionPayload { + endorsedTransaction: string; + endorserDid: string; + schema?: SchemaPayload; + credentialDefinition?: CredentialDefinitionPayload; +} + +export interface SaveSchema { + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + issuerId: string; + createdBy: string; + lastChangedBy: string; + publisherDid: string; + orgId: string; + ledgerId: string; + type?: string; +} + +export interface saveCredDef { + schemaLedgerId: string; + tag: string; + credentialDefinitionId: string; + revocable: boolean; + createdBy: string; + lastChangedBy: string; + orgId: string; + schemaId: string; +} + +export interface EndorsementTransactionPayloadDetails { + id: string; + endorserDid: string; + authorDid: string; + requestPayload: string; + responsePayload: string; + type: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date | null; + status: string; + ecosystemOrgId: string; + requestBody: unknown; + ecosystemOrgs?: { + orgId: string; + }; +} + +export interface CreateEcosystem { + name?: string; + description?: string; + tags?: string; + logoUrl?: string; + userId?: string; + logo?: string; + orgName?: string; + orgDid?: string; + orgId?: string; + autoEndorsement?: boolean; + lastChangedBy?: string; + ledgers?: string[]; +} + +export interface OrganizationData { + id: string; + createDateTime: string; + createdBy: string; + lastChangedDateTime: string; + lastChangedBy: string; + name: string; + description: string; + orgSlug: string; + logoUrl: string; + website: string; + publicProfile: boolean; + org_agents: OrgAgent[]; +} + +export interface OrgAgent { + id: string; + orgDid: string; + verkey: string; + agentEndPoint: string; + isDidPublic: boolean; + agentSpinUpStatus: number; + walletName: string; + tenantId: string; + agentsTypeId: string; + orgId: string; + orgAgentTypeId: string; + ledgerId?: string; + ledgers?: LedgerDetails; +} + +export interface LedgerDetails { + id: string; + name: string; + indyNamespace: string; + networkUrl: string; +} + +export interface EcosystemInvitationDetails { + name: string; + id: string; + logoUrl: string; + description?: string; + tags?: string; + createDateTime?: Date; + createdBy?: string; + lastChangedDateTime?: Date; + lastChangedBy?: string; + deletedAt?: Date; + autoEndorsement?: boolean; + ledgers: Prisma.JsonValue; + networkDetails?: LedgerDetails[]; +} + +export interface InvitationResponse { + id: string; + email: string; + status: string; + ecosystemId: string; + userId: string; + orgId: string; + deletedAt: Date; + ecosystem: EcosystemInvitationDetails; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; +} + +export interface IEcosystemInvitation { + invitations: InvitationResponse[]; + totalPages: number; +} + +export interface ITransactionData { + endorsementId: string; + ecosystemId: string; + ecosystemLeadAgentEndPoint?: string; + orgId?: string; + user?: IUserRequestInterface +} + +export interface IEcosystemDashboard { + ecosystem: IEcosystemDetails[]; + membersCount: number; + endorsementsCount: number; + ecosystemLead: EcosystemLeadDetails; +}; + +interface IEcosystemDetails { + id: string; + name: string; + description: string; + tags: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + logoUrl: string; + autoEndorsement: boolean; + ledgers: string[]; +} + +interface EcosystemLeadDetails { + role: string; + orgName: string; + config: IEcosystemConfigDetails[]; +} +interface IEcosystemConfigDetails { + id: string; + key: string; + value: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; +} + +export interface IEditEcosystem extends IEcosystem { + lastChangedDateTime: Date; + lastChangedBy: string; +} + +export interface IEcosystem { + id: string; + name: string; + description: string; + tags: string; + createDateTime: Date; + createdBy: string; + logoUrl: string; + autoEndorsement: boolean; + ledgers: Prisma.JsonValue; +} + +export interface IEcosystemInvitations { + id: string; + email: string; + status: string; + ecosystemId: string; + userId: string; + orgId: string; + ecosystem: EcosystemInvitationDetails; + createDateTime: Date; + createdBy: string; +} + +interface IAttributes { + isRequired: boolean; + displayName: string; + attributeName: string; + schemaDataType: string; +} + +interface ISChemaItems { + id: string; + createDateTime: string; + createdBy: string; + lastChangedDateTime: string; + lastChangedBy: string; + name: string; + version: string; + attributes: IAttributes[]; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; + ledgerId: string; +} + +export interface ISchemaResponse { + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + nextPage: number; + previousPage: number; + lastPage: number; + data: ISChemaItems[]; +} + +export interface IEcosystemList { + orgId: string, + pageNumber: number; + pageSize: number; + search: string; +} + +export interface IEcosystemLeadOrgs { + organizationIds: string[]; + ecosystemId: string; + orgId: string; + userId: string; +} + +export interface IEcosystemOrgs { + orgId: string, + ecosystemId: string, + ecosystemRoleId: string, + status: string, + deploymentMode: string, + createdBy: string, + lastChangedBy: string +} +export interface IEcosystemOrgsData extends IEcosystemOrgs { + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; +} + +export interface IEcosystemOrgDetails { + count: Prisma.BatchPayload; + ecosystemOrgs: IEcosystemOrgsData[]; +} + +export interface IEcosystemEndorsementFlag { + autoEndorsement: boolean; +} + + +interface IEcosystemRole { + id: string; + name: string; + description: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; +} + +interface IEcosystemMemberOrgs extends IEcosystemOrgs{ + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; + ecosystemRole: IEcosystemRole; +} + +export interface IEcosystemData { + id: string; + name: string; + description: string; + tags: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + logoUrl: string; + autoEndorsement: boolean; + ledgers: Prisma.JsonValue; + ecosystemOrgs: IEcosystemMemberOrgs[]; +} + +interface IW3CAttributeValue { + attributeName: string; + schemaDataType: W3CSchemaDataType; + displayName: string; + isRequired: boolean; +} + +export interface ISubmitIndySchema { + schemaVersion?: string; + schemaName: string; + attributes: IAttributeValue[]; + orgId?: string; + orgDid?: string; +} +export interface ISubmitW3CSchema { + attributes: IW3CAttributeValue[]; + schemaName: string; + description: string; + schemaType: JSONSchemaType; +} +export interface ISubmitSchemaEndorsement { + type: SchemaTypeEnum; + schemaPayload: ISubmitIndySchema | ISubmitW3CSchema; +} + +export interface IschemaPayload { + schemaDetails: ISubmitSchemaEndorsement, + user: IUserRequestInterface, + orgId: string +} diff --git a/apps/ecosystem/interfaces/ecosystemMembers.interface.ts b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts new file mode 100644 index 000000000..58b12fd4b --- /dev/null +++ b/apps/ecosystem/interfaces/ecosystemMembers.interface.ts @@ -0,0 +1,9 @@ +export interface EcosystemMembersPayload { + ecosystemId: string; + orgId: string, + pageNumber: number; + pageSize: number; + search: string; + sortBy: string; + sortField: string; +} \ No newline at end of file diff --git a/apps/ecosystem/interfaces/endorsements.interface.ts b/apps/ecosystem/interfaces/endorsements.interface.ts new file mode 100644 index 000000000..fe3ba0356 --- /dev/null +++ b/apps/ecosystem/interfaces/endorsements.interface.ts @@ -0,0 +1,36 @@ +export interface GetEndorsementsPayload { + ecosystemId: string; + orgId: string; + status: string; + pageNumber: number; + pageSize: number; + search: string; + type: string; + } + + export interface GetAllSchemaList { + ecosystemId: string; + orgId: string; + status: string; + pageNumber: number; + pageSize: number; + search: string; + } + + interface ISchemaResult { + createDateTime: Date; + createdBy: string; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; + } + + export interface ISchemasResponse { + schemasCount: number; + schemasResult: ISchemaResult[]; + } + \ No newline at end of file diff --git a/apps/ecosystem/interfaces/invitations.interface.ts b/apps/ecosystem/interfaces/invitations.interface.ts new file mode 100644 index 000000000..932e490ea --- /dev/null +++ b/apps/ecosystem/interfaces/invitations.interface.ts @@ -0,0 +1,9 @@ + + +export interface FetchInvitationsPayload { + ecosystemId: string; + userId: string, + pageNumber: number; + pageSize: number; + search: string +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.controller.spec.ts b/apps/ecosystem/src/ecosystem.controller.spec.ts new file mode 100644 index 000000000..653289261 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; + +describe('EcosystemController', () => { + let ecosystemController: EcosystemController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [EcosystemController], + providers: [EcosystemService] + }).compile(); + + ecosystemController = app.get(EcosystemController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(ecosystemController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/apps/ecosystem/src/ecosystem.controller.ts b/apps/ecosystem/src/ecosystem.controller.ts new file mode 100644 index 000000000..f0032497b --- /dev/null +++ b/apps/ecosystem/src/ecosystem.controller.ts @@ -0,0 +1,244 @@ +import { Controller, Logger, Body } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { EcosystemService } from './ecosystem.service'; +import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; +import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; +import { GetEndorsementsPayload, ISchemasResponse } from '../interfaces/endorsements.interface'; +import { IEcosystemDashboard, RequestCredDeffEndorsement, IEcosystem, IEcosystemInvitation, IEcosystemInvitations, IEditEcosystem, IEndorsementTransaction, IEcosystemList, IEcosystemLeadOrgs, IRequestSchemaEndorsement } from '../interfaces/ecosystem.interfaces'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { user } from '@prisma/client'; +import { IUserRequestInterface } from 'apps/ledger/src/credential-definition/interfaces'; +// eslint-disable-next-line camelcase + +@Controller() +export class EcosystemController { + constructor(private readonly ecosystemService: EcosystemService) {} + private readonly logger = new Logger('EcosystemController'); + + /** + * Description: create new ecosystem + * @param payload Registration Details + * @returns Get created ecosystem details + */ + + @MessagePattern({ cmd: 'create-ecosystem' }) + async createEcosystem(@Body() payload: { createEcosystemDto }): Promise { + return this.ecosystemService.createEcosystem(payload.createEcosystemDto); + } + + /** + * Description: edit ecosystem + * @param payload updation Details + * @returns Get updated ecosystem details + */ + @MessagePattern({ cmd: 'edit-ecosystem' }) + async editEcosystem(@Body() payload: { editEcosystemDto; ecosystemId }): Promise { + return this.ecosystemService.editEcosystem(payload.editEcosystemDto, payload.ecosystemId); + } + + /** + * Description: get all ecosystems + * @param payload Registration Details + * @returns Get all ecosystem details + */ + @MessagePattern({ cmd: 'get-all-ecosystem' }) + async getAllEcosystems(@Body() payload: IEcosystemList): Promise { + return this.ecosystemService.getAllEcosystem(payload); + } + + /** + * Description: get ecosystems dashboard details + * @returns Get ecosystem dashboard details + */ + @MessagePattern({ cmd: 'get-ecosystem-dashboard-details' }) + async getEcosystemDashboardDetails(payload: { ecosystemId: string; orgId: string }): Promise { + return this.ecosystemService.getEcosystemDashboardDetails(payload.ecosystemId); + } + + /** + * Description: get ecosystem invitations + * @returns Get sent invitation details + */ + @MessagePattern({ cmd: 'get-ecosystem-invitations' }) + async getEcosystemInvitations( + @Body() payload: { userEmail: string; status: string; pageNumber: number; pageSize: number; search: string } + ): Promise { + return this.ecosystemService.getEcosystemInvitations( + payload.userEmail, + payload.status, + payload.pageNumber, + payload.pageSize, + payload.search + ); + } + + /** + * + * @param payload + * @returns ecosystem members list + */ + @MessagePattern({ cmd: 'fetch-ecosystem-members' }) + async getEcosystemMembers(@Body() payload: EcosystemMembersPayload): Promise { + return this.ecosystemService.getEcosystemMembers(payload); + } + + /** + * + * @param payload + * @returns Sent ecosystem invitations status + */ + @MessagePattern({ cmd: 'send-ecosystem-invitation' }) + async createInvitation(payload: { + bulkInvitationDto: BulkSendInvitationDto; + userId: string; + userEmail: string; + orgId: string; + }): Promise { + return this.ecosystemService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail, payload.orgId); + } + + + /** + * + * @param orgId + * @param ecosystemId + */ + @MessagePattern({ cmd: 'add-organization-in-ecosystem' }) + async addOrganizationsInEcosystem( + ecosystemLeadOrgs: IEcosystemLeadOrgs + ): Promise<{ results: { statusCode: number, message: string, error?: string, data?: { orgId: string } }[], statusCode: number, message: string }> { + return this.ecosystemService.addOrganizationsInEcosystem(ecosystemLeadOrgs); + } + + /** + * + * @param payload + * @returns Ecosystem invitation status fetch-ecosystem-users + */ + @MessagePattern({ cmd: 'accept-reject-ecosystem-invitations' }) + async acceptRejectEcosystemInvitations(payload: { + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + userEmail: string + }): Promise { + return this.ecosystemService.acceptRejectEcosystemInvitations(payload.acceptRejectInvitation, payload.userEmail); + } + + @MessagePattern({ cmd: 'get-ecosystem-records' }) + async getEcosystemsByOrgId(payload: { orgId: string, userId: string }): Promise { + const { orgId } = payload; + return this.ecosystemService.getEcosystems(orgId); + } + + @MessagePattern({ cmd: 'get-sent-invitations-ecosystemId' }) + async getInvitationsByOrgId(@Body() payload: FetchInvitationsPayload): Promise { + return this.ecosystemService.getInvitationsByEcosystemId(payload); + } + + @MessagePattern({ cmd: 'get-endorsement-transactions' }) + async getEndorsementTransactions(@Body() payload: GetEndorsementsPayload): Promise { + return this.ecosystemService.getEndorsementTransactions(payload); + } + + @MessagePattern({ cmd: 'get-all-ecosystem-schemas' }) + async getAllEcosystemSchemas(@Body() payload: GetEndorsementsPayload): Promise { + return this.ecosystemService.getAllEcosystemSchemas(payload); + } + + @MessagePattern({ cmd: 'delete-ecosystem-invitations' }) + async deleteInvitation(@Body() payload: { invitationId: string }): Promise { + return this.ecosystemService.deleteEcosystemInvitations(payload.invitationId); + } + @MessagePattern({ cmd: 'fetch-ecosystem-org-data' }) + async fetchEcosystemOrg(@Body() payload: { ecosystemId: string; orgId: string }): Promise { + return this.ecosystemService.fetchEcosystemOrg(payload); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'schema-endorsement-request' }) + async schemaEndorsementRequest(payload: { + requestSchemaPayload: IRequestSchemaEndorsement; + user: user; + orgId: string; + ecosystemId: string; + }): Promise { + const { requestSchemaPayload, user, orgId, ecosystemId } = payload; + return this.ecosystemService.requestSchemaEndorsement(requestSchemaPayload, user, orgId, ecosystemId); + } + + /** + * + * @param payload + * @returns Schema endorsement request + */ + @MessagePattern({ cmd: 'credDef-endorsement-request' }) + async credDefEndorsementRequest(payload: { + requestCredDefPayload: RequestCredDeffEndorsement; + orgId: string; + ecosystemId: string; + }): Promise { + return this.ecosystemService.requestCredDeffEndorsement( + payload.requestCredDefPayload, + payload.orgId, + payload.ecosystemId + ); + } + + /** + * + * @param payload + * @returns sign endorsement request + */ + @MessagePattern({ cmd: 'sign-endorsement-transaction' }) + async signTransaction(payload: { endorsementId: string; ecosystemId: string}): Promise { + const { endorsementId, ecosystemId } = payload; + return this.ecosystemService.signTransaction(endorsementId, ecosystemId); + } + + /** + * + * @param payload + * @returns submit endorsement request + */ + @MessagePattern({ cmd: 'submit-endorsement-transaction' }) + async submitTransaction(payload: { endorsementId: string; ecosystemId: string; orgId: string, user: IUserRequestInterface}): Promise { + const { endorsementId, ecosystemId, orgId, user } = payload; + return this.ecosystemService.submitTransaction({ + endorsementId, + ecosystemId, + orgId, + user + }); + } + + /** + * + * @param payload + * @returns auto sign and submit endorsement request + */ + @MessagePattern({ cmd: 'auto-endorsement-transaction' }) + async autoSignAndSubmitTransaction(): Promise { + return this.ecosystemService.autoSignAndSubmitTransaction(); + } + + /** + * + * @param payload + * @returns Declien Endorsement Transaction status + */ + @MessagePattern({ cmd: 'decline-endorsement-transaction' }) + async declineEndorsementRequestByLead(payload: { ecosystemId: string; endorsementId: string }): Promise { + return this.ecosystemService.declineEndorsementRequestByLead(payload.ecosystemId, payload.endorsementId); + } + + @MessagePattern({ cmd: 'delete-org-from-ecosystem' }) + async deleteOrgFromEcosystem(payload: { orgId: string, userDetails: user}): Promise { + const { orgId, userDetails } = payload; + return this.ecosystemService.deleteOrgFromEcosystem(orgId, userDetails); + } +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.module.ts b/apps/ecosystem/src/ecosystem.module.ts new file mode 100644 index 000000000..82cf3eb4d --- /dev/null +++ b/apps/ecosystem/src/ecosystem.module.ts @@ -0,0 +1,29 @@ +import { Logger, Module } from '@nestjs/common'; +import { EcosystemController } from './ecosystem.controller'; +import { EcosystemService } from './ecosystem.service'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { CommonModule} from '@credebl/common'; +import { EcosystemRepository } from './ecosystem.repository'; +import { PrismaService } from '@credebl/prisma-service'; +import { CacheModule } from '@nestjs/cache-manager'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { UserActivityRepository } from 'libs/user-activity/repositories'; +import { CommonConstants } from '@credebl/common/common.constant'; + +@Module({ + imports: [ + ClientsModule.register([ + { + name: 'NATS_CLIENT', + transport: Transport.NATS, + options: getNatsOptions(CommonConstants.ECOSYSTEM_SERVICE, process.env.ECOSYSTEM_NKEY_SEED) + } + ]), + + CommonModule, + CacheModule.register() + ], + controllers: [EcosystemController], + providers: [EcosystemService, UserActivityRepository, PrismaService, Logger, EcosystemRepository] +}) +export class EcosystemModule { } \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.repository.ts b/apps/ecosystem/src/ecosystem.repository.ts new file mode 100644 index 000000000..eb1264c0e --- /dev/null +++ b/apps/ecosystem/src/ecosystem.repository.ts @@ -0,0 +1,1483 @@ +import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { PrismaService } from '@credebl/prisma-service'; +// eslint-disable-next-line camelcase +import { Prisma, credential_definition, ecosystem, ecosystem_config, ecosystem_invitations, ecosystem_orgs, ecosystem_roles, endorsement_transaction, org_agents, org_roles, organisation, platform_config, schema, user_org_roles } from '@prisma/client'; +import { DeploymentModeType, EcosystemInvitationStatus, EcosystemOrgStatus, EcosystemRoles, endorsementTransactionStatus, endorsementTransactionType } from '../enums/ecosystem.enum'; +import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; +import { CreateEcosystem, IEcosystemData, IEcosystemInvitation, IEcosystemOrgs, IEcosystemOrgsData, SaveSchema, SchemaTransactionResponse, saveCredDef } from '../interfaces/ecosystem.interfaces'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { NotFoundException } from '@nestjs/common'; +import { CommonConstants } from '@credebl/common/common.constant'; +import { GetAllSchemaList, ISchemasResponse } from '../interfaces/endorsements.interface'; +import { SortValue } from '@credebl/enum/enum'; +import { IEcosystemDataDeletionResults } from '@credebl/common/interfaces/ecosystem.interface'; +// eslint-disable-next-line camelcase + +@Injectable() +export class EcosystemRepository { + + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) { } + + /** + * Description: create ecosystem + * @param createEcosystemDto + * @returns ecosystem + */ + // eslint-disable-next-line camelcase + async createNewEcosystem(createEcosystemDto: CreateEcosystem, ecosystemLedgers: string[]): Promise { + try { + const transaction = await this.prisma.$transaction(async (prisma) => { + const { name, description, userId, logo, tags, orgId, autoEndorsement } = createEcosystemDto; + const createdEcosystem = await prisma.ecosystem.create({ + data: { + name, + description, + tags, + autoEndorsement, + logoUrl: logo, + createdBy: userId, + lastChangedBy: userId, + ledgers: ecosystemLedgers + } + }); + let ecosystemUser; + if (createdEcosystem) { + ecosystemUser = await prisma.ecosystem_users.create({ + data: { + userId: String(userId), + ecosystemId: createdEcosystem.id, + createdBy: userId, + lastChangedBy: userId + } + }); + } + + if (ecosystemUser) { + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }); + ecosystemUser = await prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + status: EcosystemOrgStatus.ACTIVE, + ecosystemId: createdEcosystem.id, + ecosystemRoleId: ecosystemRoleDetails.id, + deploymentMode: DeploymentModeType.PROVIDER_HOSTED, + createdBy: userId, + lastChangedBy: userId + } + }); + } + return createdEcosystem; + }); + + // To return selective object data + delete transaction.lastChangedDateTime; + delete transaction.lastChangedBy; + delete transaction.deletedAt; + + return transaction; + } catch (error) { + this.logger.error(`Error in create ecosystem transaction: ${error.message}`); + throw error; + } + } + + //eslint-disable-next-line camelcase + async addOrganizationInEcosystem(orgs: IEcosystemOrgs[]): Promise { + try { + + const result = await this.prisma.ecosystem_orgs.createMany({ + data: orgs + }); + + return result; + + } catch (error) { + this.logger.error(`Error in add organization ecosystem: ${error.message}`); + throw error; + } + } + + async getEcosystemOrgs(orgIds: string[], ecosystemId: string): Promise { + try { + const result = await this.prisma.ecosystem_orgs.findMany({ + where: { + orgId: { + in: orgIds + }, + ecosystemId + } + }); + + return result; + + } catch (error) { + this.logger.error(`Error in fetch ecosystem orgs: ${error.message}`); + throw error; + } + } + + async getEcosystemsCount(orgId: string): Promise { + try { + const ecosystemsCount = await this.prisma.ecosystem.count({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + } + }); + return ecosystemsCount; + } catch (error) { + this.logger.error(`[get all ecosystems by org Id] - error: ${JSON.stringify(error)}`); + throw error; + } + } + + async checkOrgExists(orgId: string): Promise { + try { + const isOrgExists = await this.prisma.organisation.findUnique({ + where: { + id: orgId + } + }); + return isOrgExists; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + + // eslint-disable-next-line camelcase + async checkOrgExistsInEcosystem(orgId: string, ecosystemId: string): Promise { + try { + const existingOrgs = await this.prisma.ecosystem_orgs.findFirst({ + where: { + ecosystemId, + orgId + } + }); + return existingOrgs; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + /** + * Description: Edit ecosystem by Id + * @param editEcosystemDto + * @returns ecosystem details + */ + // eslint-disable-next-line camelcase + async updateEcosystemById( + data: object, + ecosystemId: string): Promise { + try { + const editEcosystem = await this.prisma.ecosystem.update({ + where: { id: ecosystemId }, + data + }); + return editEcosystem; + } catch (error) { + this.logger.error(`Error in edit ecosystem transaction: ${error.message}`); + throw error; + } + } + + /** + * + * + * @returns Get all ecosystem details + */ + async getAllEcosystemDetails( + orgId: string, + pageNumber: number, + pageSize: number, + search: string + ): Promise { + try { + const sortByName = SortValue.DESC; + + const result = await Promise.all([ + this.prisma.ecosystem.findMany({ + where: { + ecosystemOrgs: { + some: { + orgId + } + }, + OR: [ + { name: { contains: search, mode: 'insensitive' } }, + { description: { contains: search, mode: 'insensitive' } } + ] + }, + select: { + id: true, + name: true, + description: true, + logoUrl: true, + createDateTime: true, + lastChangedDateTime: true, + createdBy: true, + autoEndorsement: true, + ecosystemOrgs: { + where: { + orgId + }, + select: { + id: true, + orgId: true, + status: true, + createDateTime: true, + lastChangedDateTime: true, + ecosystemId: true, + ecosystemRoleId: true, + ecosystemRole: true + } + } + }, + take: Number(pageSize), + skip: (pageNumber - 1) * pageSize, + orderBy: { + name: sortByName + } + }), + this.prisma.ecosystem.count({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + } + }) + ]); + + return result; + } catch (error) { + this.logger.error(`Error in get all ecosystem transaction: ${error.message}`); + throw error; + } + } + + + /** + * + * @param ecosystemId + * @returns Get specific ecosystem details + */ + async getEcosystemDetails(ecosystemId: string): Promise { + try { + return this.prisma.ecosystem.findFirst({ + where: { + id: ecosystemId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + async checkEcosystemExist(name: string, ecosystemId:string): Promise { + try { + return this.prisma.ecosystem.findMany({ + where: { + id:ecosystemId, + name + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + async checkEcosystemNameExist(name: string): Promise { + try { + return this.prisma.ecosystem.findFirst({ + where: { + name + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + + /** + * + * @param orgId + * @returns Get specific organization details from ecosystem + */ + // eslint-disable-next-line camelcase + async checkEcosystemOrgs(orgId: string): Promise { + try { + if (!orgId) { + throw new BadRequestException(ResponseMessages.ecosystem.error.invalidOrgId); + } + return this.prisma.ecosystem_orgs.findMany({ + where: { + orgId + }, + include: { + ecosystemRole: true + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * + * @returns Get ecosystem dashboard card count + */ + // eslint-disable-next-line camelcase + async getEcosystemDashboardDetails(ecosystemId: string): Promise<{ membersCount: number; endorsementsCount: number; ecosystemConfigData: ecosystem_config[] }> { + try { + const membersCount = await this.getEcosystemMembersCount(ecosystemId); + const endorsementsCount = await this.getEcosystemEndorsementsCount(ecosystemId); + const ecosystemConfigData = await this.getEcosystemConfig(); + return { + membersCount, + endorsementsCount, + ecosystemConfigData + }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getEcosystemConfig(): Promise { + try { + const getEcosystemConfigDetails = await this.prisma.ecosystem_config.findMany(); + return getEcosystemConfigDetails; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getSpecificEcosystemConfig(key: string): Promise { + try { + return await this.prisma.ecosystem_config.findFirst( + { + where: { + key + } + } + ); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getEcosystemMembersCount(ecosystemId: string): Promise { + try { + const membersCount = await this.prisma.ecosystem_orgs.count( + { + where: { + ecosystemId + } + } + ); + return membersCount; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getEcosystemEndorsementsCount(ecosystemId: string): Promise { + try { + const endorsementsCount = await this.prisma.endorsement_transaction.count({ + where: { + ecosystemOrgs: { + ecosystemId + + } + } + }); + return endorsementsCount; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * + * @param queryObject + * @returns Get all ecosystem invitations + */ + async getEcosystemInvitations( + queryObject: object + // eslint-disable-next-line camelcase + ): Promise { + try { + return this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: true + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + + /** + * + * @param id + * @returns Invitation details + */ + // eslint-disable-next-line camelcase + async getEcosystemInvitationById(id: string, email: string): Promise { + try { + return this.prisma.ecosystem_invitations.findUnique({ + where: { + id, + email + }, + include: { + ecosystem: true + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * + * @param queryObject + * @param data + * @returns Updated ecosystem invitation response + */ + async updateEcosystemInvitation(id: string, data: object): Promise { + try { + return this.prisma.ecosystem_invitations.update({ + where: { + id: String(id) + }, + data: { + ...data + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getEcosystemRole(name: string): Promise { + try { + const getEcosytemRoles = this.prisma.ecosystem_roles.findFirst({ + where: { + name + } + }); + return getEcosytemRoles; + } catch (error) { + this.logger.error(`getEcosystemRole: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async updateEcosystemOrgs(createEcosystemOrgsDto: updateEcosystemOrgsDto): Promise { + try { + const { orgId, status, ecosystemRoleId, ecosystemId, createdBy, lastChangedBy } = createEcosystemOrgsDto; + + return this.prisma.ecosystem_orgs.create({ + data: { + orgId: String(orgId), + ecosystemId, + status, + ecosystemRoleId, + deploymentMode: DeploymentModeType.PROVIDER_HOSTED, + createdBy, + lastChangedBy + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * + * @param email + * @param ecosystemId + * @param userId + * @returns + */ + async createSendInvitation( + email: string, + ecosystemId: string, + userId: string + // eslint-disable-next-line camelcase + ): Promise { + try { + + return this.prisma.ecosystem_invitations.create({ + data: { + email, + userId, + ecosystem: { connect: { id: ecosystemId } }, + status: EcosystemInvitationStatus.PENDING, + orgId: '', + createdBy: userId, + lastChangedBy: userId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getInvitationsByEcosystemId(ecosystemId: string, pageNumber: number, pageSize: number, search = ''): Promise { + try { + const query = { + ecosystemId, + OR: [ + { email: { contains: search, mode: 'insensitive' } }, + { status: { contains: search, mode: 'insensitive' } } + ] + }; + + return await this.getEcosystemInvitationsPagination(query, pageNumber, pageSize); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + + /** + * + * @param queryOptions + * @param filterOptions + * @returns users list + */ + + async findEcosystemMembers( + ecosystemId: string, + pageNumber: number, + pageSize: number, + search: string, + sortBy: string, + sortField: string + ): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_orgs.findMany({ + where: { + ecosystemId, + OR: [ + { + organisation: { + name: { contains: search, mode: 'insensitive' } + } + }, + { + organisation: { + // eslint-disable-next-line camelcase + org_agents: { + some: { + orgDid: { contains: search, mode: 'insensitive' } + } + } + } + } + ] + }, + include: { + ecosystem: true, + ecosystemRole: true, + organisation: { + select: { + name: true, + orgSlug: true, + // eslint-disable-next-line camelcase + org_agents: true, + userOrgRoles: { + select: { + userId: true + } + } + } + } + }, + take: Number(pageSize), + skip: (pageNumber - 1) * pageSize, + orderBy: { + [sortField]: SortValue.ASC === sortBy ? 'asc' : 'desc' + } + }), + this.prisma.ecosystem_orgs.count({ + where: { + ecosystemId + } + }) + ]); + return result; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getEcosystemInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.ecosystem_invitations.findMany({ + where: { + ...queryObject + }, + include: { + ecosystem: { + select: { + id: true, + name: true, + logoUrl: true, + ledgers: true + } + } + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.ecosystem_invitations.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const invitations = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, invitations }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + + async fetchEcosystemOrg( + payload: object + ): Promise { + + return this.prisma.ecosystem_orgs.findFirst({ + where: { + ...payload + }, + select: { + ecosystem: true, + ecosystemRole: true, + organisation: true + } + }); + + } + + async getEndorsementsWithPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { + try { + const result = await this.prisma.$transaction([ + this.prisma.endorsement_transaction.findMany({ + where: { + ...queryObject + }, + select: { + id: true, + endorserDid: true, + authorDid: true, + status: true, + type: true, + ecosystemOrgs: { + include: { + organisation: { + select: { + name: true, + orgSlug: true + } + } + } + }, + requestPayload: true, + responsePayload: true, + createDateTime: true, + requestBody: true + }, + take: pageSize, + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }), + this.prisma.endorsement_transaction.count({ + where: { + ...queryObject + } + }) + ]); + + // eslint-disable-next-line prefer-destructuring + const transactions = result[0]; + // eslint-disable-next-line prefer-destructuring + const totalCount = result[1]; + const totalPages = Math.ceil(totalCount / pageSize); + + return { totalPages, transactions }; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + + async getAllEcosystemSchemasDetails(payload: GetAllSchemaList): Promise { + try { + const { ecosystemId, search, pageNumber, pageSize } = payload; + + const schemaDetails = await this.prisma.endorsement_transaction.findMany({ + where: { + type: endorsementTransactionType.SCHEMA, + status: endorsementTransactionStatus.SUBMITED, + ecosystemOrgs: { + ecosystem: { + id: ecosystemId + } + }, + resourceId: { + not: { + equals: null + } + } + } + }); + const schemaArray = []; + schemaDetails.map((schemaData) => schemaArray.push(schemaData.resourceId)); + const schemasResult = await this.prisma.schema.findMany({ + where: { + OR: [ + { version: { contains: search, mode: 'insensitive' } }, + { name: { contains: search, mode: 'insensitive' } }, + { schemaLedgerId: { contains: search, mode: 'insensitive' } } + ], + schemaLedgerId: { + in: schemaArray + } + }, + take: Number(pageSize), + skip: (pageNumber - 1) * pageSize, + orderBy: { + createDateTime: 'desc' + } + }); + const schemasCount = schemaArray.length; + return { schemasCount, schemasResult }; + + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * Description: Get getAgentEndPoint by orgId + * @param orgId + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async getAgentDetails(orgId: string): Promise { + try { + if (!orgId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidOrgId); + } + const agentDetails = await this.prisma.org_agents.findFirst({ + where: { + orgId: orgId.toString() + } + }); + return agentDetails; + + } catch (error) { + this.logger.error(`Error in getting getAgentEndPoint for the ecosystem: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getAllAgentDetails(orgId: string): Promise { + try { + if (!orgId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidOrgId); + } + return await this.prisma.org_agents.findMany({ + where: { + orgId: orgId.toString() + } + }); + + } catch (error) { + this.logger.error(`Error in getAllAgentDetails for the ecosystem: ${error.message} `); + throw error; + } + } + + /** + * Description: Get getAgentEndPoint by invalidEcosystemId + * @param invalidEcosystemId + * @returns Get getAgentEndPoint details + */ + // eslint-disable-next-line camelcase + async getEcosystemLeadDetails(ecosystemId: string): Promise { + try { + if (!ecosystemId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidEcosystemId); + } + const ecosystemRoleDetails = await this.prisma.ecosystem_roles.findFirst({ + where: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }); + const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ + where: { + ecosystemRoleId: ecosystemRoleDetails.id, + ecosystemId + } + }); + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getAllOrganizationsByUserId(userId: string): Promise { + try { + const getUserorgs = await this.prisma.user_org_roles.findMany({ + where: { + userId + } + }); + return getUserorgs; + + } catch (error) { + this.logger.error(`Error in getting user organizations: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getAllOrgRoles(): Promise { + try { + const getUserorgs = await this.prisma.org_roles.findMany(); + return getUserorgs; + + } catch (error) { + this.logger.error(`Error in getting organization roled: ${error.message} `); + throw error; + } + } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getPlatformConfigDetails(): Promise { + try { + + return this.prisma.platform_config.findFirst(); + + } catch (error) { + this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); + throw error; + } + } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getEcosystemConfigDetails(key: string): Promise { + try { + + return this.prisma.ecosystem_config.findFirst({ + where: { + key + } + }); + + } catch (error) { + this.logger.error(`Error in getting getPlatformConfigDetails for the ecosystem - error: ${JSON.stringify(error)}`); + throw error; + } + } + + async storeTransactionRequest( + schemaTransactionResponse: SchemaTransactionResponse, + requestBody: object, + type: endorsementTransactionType + // eslint-disable-next-line camelcase + ): Promise { + try { + const { endorserDid, authorDid, requestPayload, status, ecosystemOrgId, userId } = schemaTransactionResponse; + return await this.prisma.endorsement_transaction.create({ + data: { + endorserDid, + authorDid, + requestPayload, + status, + ecosystemOrgId, + responsePayload: '', + type, + requestBody, + resourceId: '', + createdBy: userId, + lastChangedBy: userId + } + }); + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async deleteInvitations(invitationId: string): Promise { + try { + + const findInvitation = await this.prisma.ecosystem_invitations.findUnique({ + where: { + id: invitationId, + status: EcosystemInvitationStatus.PENDING + } + }); + + if (!findInvitation) { + throw new NotFoundException('Ecosystem Invitation not found'); + } + + const deletedInvitation = await this.prisma.ecosystem_invitations.delete({ + where: { + id: invitationId, + status: EcosystemInvitationStatus.PENDING + } + }); + + delete deletedInvitation.lastChangedBy; + delete deletedInvitation.lastChangedDateTime; + return deletedInvitation; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw error; + } + } + + + // eslint-disable-next-line camelcase + async getEcosystemOrgDetailsbyId(orgId: string, ecosystemId: string): Promise { + try { + //need to change + const ecosystemLeadDetails = await this.prisma.ecosystem_orgs.findFirst({ + where: { + orgId, + ecosystemId + } + }); + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; + } + } + // eslint-disable-next-line camelcase + async getEndorsementTransactionById(endorsementId: string, status: endorsementTransactionStatus): Promise { + try { + const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findUnique({ + where: { + id: endorsementId, + status + }, + include: { + ecosystemOrgs: { + select: { + orgId: true + } + } + } + }); + + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getTransactionDetailsByEndorsementId(endorsementId: string): Promise { + try { + const endorsementTransactionDetails = await this.prisma.endorsement_transaction.findUnique({ + where: { + id: endorsementId + }, + include: { + ecosystemOrgs: { + select: { + orgId: true + } + } + } + }); + + return endorsementTransactionDetails; + + } catch (error) { + this.logger.error(`Error in getting endorsement transaction details: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async getEndorsementTransactionByIdAndType(endorsementId: string, type: endorsementTransactionType): Promise { + try { + const ecosystemLeadDetails = await this.prisma.endorsement_transaction.findUnique({ + where: { + id: endorsementId, + type + }, + include: { + ecosystemOrgs: { + select: { + orgId: true + } + } + } + }); + + return ecosystemLeadDetails; + + } catch (error) { + this.logger.error(`Error in getting ecosystem lead details for the ecosystem: ${error.message} `); + throw error; + } + } + + + // eslint-disable-next-line camelcase + async findRecordsByNameAndVersion(name: string, version: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'name' = ${name} AND "requestBody"->>'version' = ${version}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem schema: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async findSchemaRecordsBySchemaName(schemaName: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'schemaName' = ${schemaName}`; + } catch (error) { + this.logger.error(`Error in getting w3c schema: ${error.message} `); + throw error; + } + } + + // eslint-disable-next-line camelcase + async findRecordsByCredDefTag(tag: string): Promise { + try { + return this.prisma.$queryRaw`SELECT * FROM endorsement_transaction WHERE "requestBody"->>'tag' = ${tag}`; + } catch (error) { + this.logger.error(`Error in getting ecosystem credential-definition: ${error.message} `); + throw error; + } + } + + async updateTransactionDetails( + endorsementId: string, + schemaTransactionRequest: string + + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + responsePayload: schemaTransactionRequest, + status: endorsementTransactionStatus.SIGNED + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + + async updateTransactionStatus( + endorsementId: string, + status: endorsementTransactionStatus + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + + async updateResourse( + endorsementId: string, + resourceId: string + // eslint-disable-next-line camelcase, + ): Promise { + try { + const updatedTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + resourceId + } + }); + + return updatedTransaction; + + } catch (error) { + this.logger.error(`Error in updating endorsement transaction: ${error.message}`); + throw error; + } + } + + async saveSchema(schemaResult: SaveSchema): Promise { + try { + const { name, version, attributes, schemaLedgerId, issuerId, createdBy, lastChangedBy, publisherDid, orgId, ledgerId, type } = schemaResult; + const saveResult = await this.prisma.schema.create({ + data: { + name, + version, + attributes, + schemaLedgerId, + issuerId, + createdBy, + lastChangedBy, + publisherDid, + orgId: String(orgId), + ledgerId, + type + } + }); + return saveResult; + } catch (error) { + this.logger.error(`Error in storing schema for submit transaction: ${error.message} `); + throw error; + } + } + + async schemaExist(schemaName: string, schemaVersion: string): Promise { + try { + return this.prisma.schema.findMany({ + where: { + name: { + contains: schemaName, + mode: 'insensitive' + }, + version: { + contains: schemaVersion, + mode: 'insensitive' + } + } + }); + } catch (error) { + this.logger.error(`Error in schemaExists: ${error}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async saveCredDef(credDefResult: saveCredDef): Promise { + try { + const { schemaLedgerId, tag, credentialDefinitionId, revocable, createdBy, lastChangedBy, orgId, schemaId } = credDefResult; + const saveResult = await this.prisma.credential_definition.create({ + data: { + schemaLedgerId, + tag, + credentialDefinitionId, + revocable, + createdBy, + lastChangedBy, + orgId, + schemaId + } + }); + return saveResult; + } catch (error) { + this.logger.error(`Error in saving credential-definition for submit transaction: ${error.message} `); + throw error; + } + } + + async getSchemaDetailsById(schemaLedgerId: string): Promise { + try { + const schemaDetails = await this.prisma.schema.findFirst({ + where: { + schemaLedgerId + } + }); + return schemaDetails; + } catch (error) { + this.logger.error(`Error in fetching schema details for submit transaction: ${error.message}`); + throw error; + } + } + + // eslint-disable-next-line camelcase + async updateEndorsementRequestStatus(ecosystemId: string, endorsementId: string): Promise { + try { + + const endorsementTransaction = await this.prisma.endorsement_transaction.findUnique({ + where: { id: endorsementId, status: endorsementTransactionStatus.REQUESTED } + }); + + if (!endorsementTransaction) { + throw new NotFoundException(ResponseMessages.ecosystem.error.EndorsementTransactionNotFoundException); + } + const { ecosystemOrgId } = endorsementTransaction; + + const endorsementTransactionEcosystemOrg = await this.prisma.ecosystem_orgs.findUnique({ + where: { id: ecosystemOrgId } + }); + + if (endorsementTransactionEcosystemOrg.ecosystemId === ecosystemId) { + const updatedEndorsementTransaction = await this.prisma.endorsement_transaction.update({ + where: { id: endorsementId }, + data: { + status: endorsementTransactionStatus.DECLINED + } + }); + + return updatedEndorsementTransaction; + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.OrgOrEcosystemNotFoundExceptionForEndorsementTransaction); + } + } catch (error) { + this.logger.error(`Error in updating endorsement transaction status: ${error.message}`); + throw error; + } + } + + async updateAutoSignAndSubmitTransaction(): Promise<{ + id: string; + key: string; + value: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + deletedAt: Date; + }> { + try { + + const { id, value } = await this.prisma.ecosystem_config.findFirst({ + where: { + key: `${CommonConstants.ECOSYSTEM_AUTO_ENDOSEMENT}` + } + }); + + const updatedValue = 'false' === value ? 'true' : 'false'; + + const updateEcosystemConfig = await this.prisma.ecosystem_config.update({ + where: { + id + }, + data: { + value: updatedValue + } + }); + + return updateEcosystemConfig; + + } catch (error) { + this.logger.error(`Error in update auto sign and submit flag: ${error.message}`); + throw error; + } + } + + async getOrgAgentType(orgAgentId: string): Promise { + try { + + const { agent } = await this.prisma.org_agents_type.findFirst({ + where: { + id: orgAgentId + } + }); + + return agent; + } catch (error) { + this.logger.error(`[getOrgAgentType] - error: ${JSON.stringify(error)}`); + throw error; + } + } + + async getEcosystemsByOrgId(orgId: string): Promise { + try { + + const ecosystemsDetails = await this.prisma.ecosystem.findMany({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + + }, + include: { + ecosystemOrgs: { + include: { + ecosystemRole: true + } + } + } + }); + + return ecosystemsDetails; + } catch (error) { + this.logger.error(`Error in getting ecosystems: ${error.message}`); + throw error; + } + } + + async getOrgName(orgId: string): Promise<{ + name: string; + }> { + try { + const orgName = await this.prisma.organisation.findUnique({ + where: { + id: orgId + }, + select: { + name: true + } + }); + + return orgName; + } catch (error) { + this.logger.error(`Error in getting organization names: ${error.message}`); + throw error; + } + } + + async deleteEcosystemInvitations(orgId: string): Promise { + try { + const deletedEcosystemInvitations = await this.prisma.ecosystem_invitations.deleteMany({ + where: { + orgId + } + }); + + return deletedEcosystemInvitations; + } catch (error) { + this.logger.error(`Error in deleting ecosystem invitations: ${error.message}`); + throw error; + } + } + + async deleteMemberOrgFromEcosystem(orgId: string): Promise { + try { + return await this.prisma.$transaction(async (prisma) => { + const deletedEcosystemUsers = await prisma.ecosystem_users.deleteMany({ + where: { + ecosystem: { + ecosystemOrgs: { + some: { + orgId + } + } + } + } + }); + + const deleteEndorsementTransactions = await prisma.endorsement_transaction.deleteMany({ + where: { + ecosystemOrgs: { + orgId + } + } + }); + + const deletedEcosystemOrgs = await prisma.ecosystem_orgs.deleteMany({ + where: { + orgId + } + }); + + const deletedEcosystems = await prisma.ecosystem.deleteMany({ + where: { + ecosystemOrgs: { + some: { + orgId + } + } + } + }); + + return { deletedEcosystemUsers, deleteEndorsementTransactions, deletedEcosystemOrgs, deletedEcosystems }; + }); + } catch (error) { + this.logger.error(`Error in deleting ecosystems: ${error.message}`); + throw error; + } + } +} \ No newline at end of file diff --git a/apps/ecosystem/src/ecosystem.service.ts b/apps/ecosystem/src/ecosystem.service.ts new file mode 100644 index 000000000..ffb6ac2c0 --- /dev/null +++ b/apps/ecosystem/src/ecosystem.service.ts @@ -0,0 +1,2209 @@ +/* eslint-disable prefer-destructuring */ +// eslint-disable-next-line camelcase +import { + BadRequestException, + ConflictException, + ForbiddenException, + HttpException, + HttpStatus, + Inject, + Injectable, + InternalServerErrorException, + Logger, + NotAcceptableException, + NotFoundException +} from '@nestjs/common'; +import { EcosystemRepository } from './ecosystem.repository'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; +import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { PrismaService } from '@credebl/prisma-service'; +import { EcosystemInviteTemplate } from '../templates/EcosystemInviteTemplate'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; +import { AcceptRejectEcosystemInvitationDto } from '../dtos/accept-reject-ecosysteminvitation.dto'; +import { EcosystemConfigSettings, Invitation, LedgerLessConstant, OrgAgentType, SchemaType, SchemaTypeEnum } from '@credebl/enum/enum'; +import { + DeploymentModeType, + EcosystemOrgStatus, + EcosystemRoles, + endorsementTransactionStatus, + endorsementTransactionType +} from '../enums/ecosystem.enum'; +import { FetchInvitationsPayload } from '../interfaces/invitations.interface'; +import { EcosystemMembersPayload } from '../interfaces/ecosystemMembers.interface'; +import { + CreateEcosystem, + CredDefMessage, + IEcosystemDashboard, + LedgerDetails, + OrganizationData, + RequestCredDeffEndorsement, + SaveSchema, + SchemaMessage, + SignedTransactionMessage, + saveCredDef, + submitTransactionPayload, + IEcosystem, + IEcosystemInvitation, + IEcosystemInvitations, + IEditEcosystem, + IEndorsementTransaction, + IEcosystemList, + IEcosystemLeadOrgs, + IRequestW3CSchemaEndorsement, + ITransactionData, + IRequestIndySchemaEndorsement, + IRequestSchemaEndorsement, + IschemaPayload +} from '../interfaces/ecosystem.interfaces'; +import { GetAllSchemaList, GetEndorsementsPayload, ISchemasResponse } from '../interfaces/endorsements.interface'; +import { CommonConstants } from '@credebl/common/common.constant'; +// eslint-disable-next-line camelcase +import { + RecordType, + // eslint-disable-next-line camelcase + credential_definition, + // eslint-disable-next-line camelcase + endorsement_transaction, + // eslint-disable-next-line camelcase + org_agents, + // eslint-disable-next-line camelcase + platform_config, + schema, + user +} from '@prisma/client'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { updateEcosystemOrgsDto } from '../dtos/update-ecosystemOrgs.dto'; +import { IEcosystemDataDeletionResults, IEcosystemDetails } from '@credebl/common/interfaces/ecosystem.interface'; +import { OrgRoles } from 'libs/org-roles/enums'; +import { DeleteEcosystemMemberTemplate } from '../templates/DeleteEcosystemMemberTemplate'; +import { UserActivityRepository } from 'libs/user-activity/repositories'; +import { IOrgData } from '@credebl/common/interfaces/organization.interface'; +import { checkDidLedgerAndNetwork } from '@credebl/common/cast.helper'; + +@Injectable() +export class EcosystemService { + constructor( + @Inject('NATS_CLIENT') private readonly ecosystemServiceProxy: ClientProxy, + private readonly ecosystemRepository: EcosystemRepository, + private readonly userActivityRepository: UserActivityRepository, + private readonly logger: Logger, + private readonly prisma: PrismaService, + @Inject(CACHE_MANAGER) private cacheService: Cache + ) {} + + /** + * + * @param createEcosystemDto + * @returns + */ + + // eslint-disable-next-line camelcase + async createEcosystem(createEcosystemDto: CreateEcosystem): Promise { + try { + const ecosystemExist = await this.ecosystemRepository.checkEcosystemNameExist(createEcosystemDto.name); + + if (ecosystemExist) { + throw new ConflictException(ResponseMessages.ecosystem.error.exists); + } + + const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig( + EcosystemConfigSettings.MULTI_ECOSYSTEM + ); + + if (isMultiEcosystemEnabled && 'false' === isMultiEcosystemEnabled.value) { + const ecoOrganizationList = await this.ecosystemRepository.checkEcosystemOrgs(createEcosystemDto.orgId); + + for (const organization of ecoOrganizationList) { + if (organization['ecosystemRole']['name'] === EcosystemRoles.ECOSYSTEM_MEMBER) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + } + } + } + + const orgDetails: OrganizationData = await this.getOrganizationDetails( + createEcosystemDto.orgId, + createEcosystemDto.userId + ); + + if (!orgDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgNotExist); + } + + if (0 === orgDetails.org_agents.length) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgDidNotExist); + } + + const ecosystemLedgers = orgDetails.org_agents.map((agent) => agent.ledgers?.id); + + const createEcosystem = await this.ecosystemRepository.createNewEcosystem(createEcosystemDto, ecosystemLedgers); + if (!createEcosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.notCreated); + } + + return createEcosystem; + } catch (error) { + this.logger.error(`createEcosystem: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async getOrganizationDetails(orgId: string, userId: string): Promise { + const pattern = { cmd: 'get-organization-by-id' }; + const payload = { orgId, userId }; + + const orgData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + + return orgData; + } + + async getEcosystems(orgId: string): Promise { + try { + return await this.ecosystemRepository.getEcosystemsCount(orgId); + } catch (error) { + this.logger.error(`[getEcosystemsCount ] [NATS call]- error in get ecosystems count : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param editEcosystemDto + * @returns + */ + + // eslint-disable-next-line camelcase + async editEcosystem(editEcosystemDto: CreateEcosystem, ecosystemId: string): Promise { + try { + const { name, description, tags, logo, autoEndorsement, userId } = editEcosystemDto; + + const updateData: CreateEcosystem = { + lastChangedBy: userId + }; + + if (name) { + updateData.name = name; + } + + if (description) { + updateData.description = description; + } + + if (tags) { + updateData.tags = tags; + } + + if (logo) { + updateData.logoUrl = logo; + } + + if ('' !== autoEndorsement.toString()) { + updateData.autoEndorsement = autoEndorsement; + } + + const ecosystemExist = await this.ecosystemRepository.checkEcosystemExist(editEcosystemDto.name, ecosystemId); + + if (0 === ecosystemExist.length) { + const ecosystemExist = await this.ecosystemRepository.checkEcosystemNameExist(editEcosystemDto.name); + if (ecosystemExist) { + throw new ConflictException(ResponseMessages.ecosystem.error.exists); + } + } + + const editEcosystem = await this.ecosystemRepository.updateEcosystemById(updateData, ecosystemId); + if (!editEcosystem) { + throw new NotFoundException(ResponseMessages.ecosystem.error.update); + } + + // Removed unnecessary key from object + delete editEcosystem.deletedAt; + + return editEcosystem; + } catch (error) { + this.logger.error(`In update ecosystem : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * + * @returns all ecosystem details + */ + + async getAllEcosystem(payload: IEcosystemList): Promise { + try { + const { orgId, pageNumber, pageSize, search } = payload; + + const getEcosystemOrgs = await this.ecosystemRepository.getAllEcosystemDetails( + orgId, + pageNumber, + pageSize, + search + ); + + const ecosystemListDetails = { + totalItems: getEcosystemOrgs[1], + hasNextPage: payload.pageSize * payload.pageNumber < getEcosystemOrgs[1], + hasPreviousPage: 1 < payload.pageNumber, + nextPage: Number(payload.pageNumber) + 1, + previousPage: payload.pageNumber - 1, + lastPage: Math.ceil(getEcosystemOrgs[1] / payload.pageSize), + ecosystemList: getEcosystemOrgs[0] + }; + + return ecosystemListDetails; + } catch (error) { + this.logger.error(`In fetch ecosystem list : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * + * @returns ecosystem dashboard details + */ + async getEcosystemDashboardDetails(ecosystemId: string): Promise { + try { + const endorseMemberCount = await this.ecosystemRepository.getEcosystemDashboardDetails(ecosystemId); + + const query = { + ecosystemId, + ecosystemRole: { + name: EcosystemRoles.ECOSYSTEM_LEAD + } + }; + + const ecosystemDetails = await this.ecosystemRepository.fetchEcosystemOrg(query); + + const dashboardDetails: IEcosystemDashboard = { + ecosystem: ecosystemDetails['ecosystem'], + membersCount: endorseMemberCount.membersCount, + endorsementsCount: endorseMemberCount.endorsementsCount, + ecosystemLead: { + role: ecosystemDetails['ecosystemRole']['name'], + orgName: ecosystemDetails['organisation']['name'], + config: endorseMemberCount.ecosystemConfigData + } + }; + + return dashboardDetails; + } catch (error) { + this.logger.error(`In ecosystem dashboard details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async fetchLedgerDetailsbyId(id: string): Promise { + const pattern = { cmd: 'get-network-details-by-id' }; + const payload = { id }; + + return this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + } + + /** + * Description: get an ecosystem invitation + * @returns Get sent ecosystem invitation details + */ + + // eslint-disable-next-line camelcase + async getEcosystemInvitations( + userEmail: string, + status: string, + pageNumber: number, + pageSize: number, + search: string + ): Promise { + try { + const query = { + AND: [{ email: userEmail }, { status: { contains: search, mode: 'insensitive' } }] + }; + + const ecosystemInvitations = await this.ecosystemRepository.getEcosystemInvitationsPagination( + query, + pageNumber, + pageSize + ); + + for (const invitation of ecosystemInvitations.invitations) { + const ledgerNetworks = invitation.ecosystem.ledgers; + + const ledgerData = []; + + if (Array.isArray(ledgerNetworks)) { + for (const ledger of ledgerNetworks) { + const ledgerDetails = await this.fetchLedgerDetailsbyId(ledger.toString()); + ledgerData.push(ledgerDetails); + } + invitation.ecosystem.networkDetails = ledgerData; + } + } + + return ecosystemInvitations; + } catch (error) { + this.logger.error(`In error getEcosystemInvitations: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + /** + * + * @param bulkInvitationDto + * @param userId + * @returns + */ + async createInvitation( + bulkInvitationDto: BulkSendInvitationDto, + userId: string, + userEmail: string, + orgId: string + ): Promise { + const { invitations, ecosystemId } = bulkInvitationDto; + const invitationResponse = []; + try { + const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + + if ( + !ecosystemDetails.ledgers || + (Array.isArray(ecosystemDetails.ledgers) && 0 === ecosystemDetails.ledgers.length) + ) { + const ecosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const ecosystemAgents = await this.ecosystemRepository.getAllAgentDetails(ecosystemLeadDetails.orgId); + + const ecosystemLedgers = ecosystemAgents.map((agent) => agent.ledgerId); + + const updateData: CreateEcosystem = { + ledgers: ecosystemLedgers + }; + + await this.ecosystemRepository.updateEcosystemById(updateData, ecosystemId); + } + + for (const invitation of invitations) { + const { email } = invitation; + + const isUserExist = await this.checkUserExistInPlatform(email); + + const userData = await this.getEcoUserName(userEmail); + + const { firstName } = userData; + + const orgDetails: OrganizationData = await this.getOrganizationDetails(orgId, userId); + + const isInvitationExist = await this.checkInvitationExist(email, ecosystemId); + + if (!isInvitationExist && userEmail !== invitation.email) { + const createInvitation = await this.ecosystemRepository.createSendInvitation(email, ecosystemId, userId); + // Removed unnecessary keys from object + delete createInvitation.lastChangedDateTime; + delete createInvitation.lastChangedBy; + delete createInvitation.deletedAt; + invitationResponse.push(createInvitation); + try { + await this.sendInviteEmailTemplate(email, ecosystemDetails.name, firstName, orgDetails.name, isUserExist); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); + } + } + } + return invitationResponse; + } catch (error) { + this.logger.error(`In send Invitation : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async addOrganizationsInEcosystem(ecosystemLeadOrgs: IEcosystemLeadOrgs): Promise<{ + results: { statusCode: number; message: string; error?: string; data?: { orgId: string } }[]; + statusCode: number; + message: string; + }> { + try { + const [ecosystemRoleDetails, ecosystemDetails, userOrganizations, allOrgRoles] = await Promise.all([ + this.ecosystemRepository.getEcosystemRole(EcosystemRoles.ECOSYSTEM_MEMBER), + this.ecosystemRepository.getEcosystemLeadDetails(ecosystemLeadOrgs.ecosystemId), + this.ecosystemRepository.getAllOrganizationsByUserId(ecosystemLeadOrgs.userId), + this.ecosystemRepository.getAllOrgRoles() + ]); + + const ecosystemLeadOrg = ecosystemDetails?.orgId; + const ecosystemLeadOrgLedgerDetails = await this.ecosystemRepository.getAgentDetails(ecosystemLeadOrg); + const ecosystemLeadOrgLedgerId = ecosystemLeadOrgLedgerDetails?.ledgerId; + + const ownerRoleId = allOrgRoles.find((role) => OrgRoles.OWNER === role.name)?.id; + const ownerOrganizations = userOrganizations + .filter((org) => org.orgRoleId === ownerRoleId) + .map((org) => org.orgId); + + const { organizationIds } = ecosystemLeadOrgs; + const errorOrgs: { statusCode: number; message: string; error?: string; data?: { orgId: string } }[] = []; + const addedOrgs = []; + let successCount: number = 0; + let errorCount: number = 0; + + for (const orgId of organizationIds) { + const result: { statusCode: number; message: string; error?: string; data?: { orgId: string } } = { + statusCode: 0, + message: '' + }; + + const orgAgentDetails = await this.ecosystemRepository.getAgentDetails(orgId); + const orgLedgerId = orgAgentDetails?.ledgerId; + + if (ownerOrganizations.includes(orgId)) { + if (orgAgentDetails?.orgDid) { + const existingOrg = await this.ecosystemRepository.checkOrgExistsInEcosystem( + orgId, + ecosystemLeadOrgs.ecosystemId + ); + if (orgLedgerId === ecosystemLeadOrgLedgerId) { + if (!existingOrg) { + addedOrgs.push({ + orgId, + ecosystemId: ecosystemLeadOrgs.ecosystemId, + ecosystemRoleId: ecosystemRoleDetails.id, + status: EcosystemOrgStatus.ACTIVE, + deploymentMode: DeploymentModeType.PROVIDER_HOSTED, + createdBy: ecosystemLeadOrgs.userId, + lastChangedBy: ecosystemLeadOrgs.userId + }); + successCount++; + } else { + result.statusCode = HttpStatus.CONFLICT; + result.message = ResponseMessages.ecosystem.error.orgAlreadyExists; + result.error = ResponseMessages.errorMessages.conflict; + result.data = { orgId }; + errorCount++; + } + } else { + result.statusCode = HttpStatus.BAD_REQUEST; + result.message = ResponseMessages.ecosystem.error.ledgerNotMatch; + result.error = ResponseMessages.errorMessages.badRequest; + result.data = { orgId }; + errorCount++; + } + } else { + result.statusCode = HttpStatus.BAD_REQUEST; + result.message = ResponseMessages.ecosystem.error.agentNotSpunUp; + result.error = ResponseMessages.errorMessages.badRequest; + result.data = { orgId }; + errorCount++; + } + } else { + result.statusCode = HttpStatus.FORBIDDEN; + result.message = ResponseMessages.ecosystem.error.userNotHaveAccess; + result.error = ResponseMessages.errorMessages.forbidden; + result.data = { orgId }; + errorCount++; + } + + if (0 !== result.statusCode) { + errorOrgs.push(result); + } + } + + let statusCode = HttpStatus.CREATED; + let message = ResponseMessages.ecosystem.success.add; + let getOrgs = []; + + if (0 < addedOrgs?.length) { + const orgs = addedOrgs?.map((item) => item.orgId); + await this.ecosystemRepository.addOrganizationInEcosystem(addedOrgs); + + //need to discuss + getOrgs = await this.ecosystemRepository.getEcosystemOrgs(orgs, ecosystemLeadOrgs.ecosystemId); + } + + const success = + 0 < getOrgs?.length + ? getOrgs?.map((item) => ({ + statusCode: HttpStatus.CREATED, + message: ResponseMessages.ecosystem.success.add, + data: { orgId: item.orgId } + })) + : []; + const finalResult = [...errorOrgs, ...success]; + + if (0 === successCount) { + statusCode = HttpStatus.BAD_REQUEST; + message = ResponseMessages.ecosystem.error.unableToAdd; + } else if (0 < errorCount && 0 < successCount) { + statusCode = HttpStatus.PARTIAL_CONTENT; + message = ResponseMessages.ecosystem.error.partiallyAdded; + } + + return { results: finalResult, statusCode, message }; + } catch (error) { + this.logger.error(`In add organizations: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async checkLedgerMatches(orgDetails: OrganizationData, ecosystemId: string): Promise { + const orgLedgers = orgDetails.org_agents.map((agent) => agent.ledgers.id); + + const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + + let isLedgerFound = false; + + for (const ledger of orgLedgers) { + // Check if the ledger is present in the ecosystem + if (Array.isArray(ecosystemDetails.ledgers) && ecosystemDetails.ledgers.includes(ledger)) { + // If a ledger is found, return true + isLedgerFound = true; + } + } + + return isLedgerFound; + } + + /** + * + * @param acceptRejectEcosystemInvitation + * @param userId + * @returns Ecosystem invitation status + */ + async acceptRejectEcosystemInvitations( + acceptRejectInvitation: AcceptRejectEcosystemInvitationDto, + email: string + ): Promise { + try { + const isMultiEcosystemEnabled = await this.ecosystemRepository.getSpecificEcosystemConfig( + EcosystemConfigSettings.MULTI_ECOSYSTEM + ); + if ( + isMultiEcosystemEnabled && + 'false' === isMultiEcosystemEnabled.value && + acceptRejectInvitation.status !== Invitation.REJECTED + ) { + const checkOrganization = await this.ecosystemRepository.checkEcosystemOrgs(acceptRejectInvitation.orgId); + if (0 < checkOrganization.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.ecosystemOrgAlready); + } + } + + const { orgId, status, invitationId, orgName, orgDid, userId } = acceptRejectInvitation; + const invitation = await this.ecosystemRepository.getEcosystemInvitationById(invitationId, email); + if (!invitation) { + throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotFound); + } + + const orgDetails: OrganizationData = await this.getOrganizationDetails( + acceptRejectInvitation.orgId, + acceptRejectInvitation.userId + ); + + if (!orgDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgNotExist); + } + + if (0 === orgDetails.org_agents.length) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgDidNotExist); + } + + const isLedgerFound = await this.checkLedgerMatches(orgDetails, invitation.ecosystemId); + + if (!isLedgerFound && Invitation.REJECTED !== status) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ledgerNotMatch); + } + + const updatedInvitation = await this.updateEcosystemInvitation(invitationId, orgId, status); + if (!updatedInvitation) { + throw new NotFoundException(ResponseMessages.ecosystem.error.invitationNotUpdate); + } + + if (status === Invitation.REJECTED) { + return ResponseMessages.ecosystem.success.invitationReject; + } + + const ecosystemRole = await this.ecosystemRepository.getEcosystemRole(EcosystemRoles.ECOSYSTEM_MEMBER); + const updateEcosystemOrgs = await this.updatedEcosystemOrgs( + orgId, + orgName, + orgDid, + invitation.ecosystemId, + ecosystemRole.id, + userId + ); + + if (!updateEcosystemOrgs) { + throw new NotFoundException(ResponseMessages.ecosystem.error.orgsNotUpdate); + } + return ResponseMessages.ecosystem.success.invitationAccept; + } catch (error) { + this.logger.error(`acceptRejectEcosystemInvitations: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async updatedEcosystemOrgs( + orgId: string, + orgName: string, + orgDid: string, + ecosystemId: string, + ecosystemRoleId: string, + userId: string + ): Promise { + try { + const data: updateEcosystemOrgsDto = { + orgId, + status: EcosystemOrgStatus.ACTIVE, + ecosystemId, + ecosystemRoleId, + createdBy: userId, + lastChangedBy: userId + }; + return await this.ecosystemRepository.updateEcosystemOrgs(data); + } catch (error) { + this.logger.error(`In newEcosystemMneber : ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param payload + * @returns Updated invitation response + */ + async updateEcosystemInvitation(invitationId: string, orgId: string, status: string): Promise { + try { + const data = { + status, + orgId: String(orgId) + }; + return this.ecosystemRepository.updateEcosystemInvitation(invitationId, data); + } catch (error) { + this.logger.error(`In updateOrgInvitation : ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param email + * @param ecosystemId + * @returns Returns boolean status for invitation + */ + async checkInvitationExist(email: string, ecosystemId: string): Promise { + try { + const query = { + email, + ecosystemId + }; + + const invitations = await this.ecosystemRepository.getEcosystemInvitations(query); + + let isPendingInvitation = false; + let isAcceptedInvitation = false; + + for (const invitation of invitations) { + if (invitation.status === Invitation.PENDING) { + isPendingInvitation = true; + } + if (invitation.status === Invitation.ACCEPTED) { + isAcceptedInvitation = true; + } + } + + if (isPendingInvitation || isAcceptedInvitation) { + return true; + } + + return false; + } catch (error) { + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param email + * @param ecosystemName + * @returns Send invitation mail + */ + async sendInviteEmailTemplate( + email: string, + ecosystemName: string, + firstName: string, + orgName: string, + isUserExist: boolean + ): Promise { + const platformConfigData = await this.prisma.platform_config.findMany(); + + const urlEmailTemplate = new EcosystemInviteTemplate(); + const emailData = new EmailDto(); + emailData.emailFrom = platformConfigData[0].emailFrom; + emailData.emailTo = email; + emailData.emailSubject = `Invitation to join an Ecosystem “${ecosystemName}” on ${process.env.PLATFORM_NAME}`; + + emailData.emailHtml = await urlEmailTemplate.sendInviteEmailTemplate( + email, + ecosystemName, + firstName, + orgName, + isUserExist + ); + + //Email is sent to user for the verification through emailData + const isEmailSent = await sendEmail(emailData); + + return isEmailSent; + } + + async checkUserExistInPlatform(email: string): Promise { + const pattern = { cmd: 'get-user-by-mail' }; + const payload = { email }; + + const userData: user = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + + if (userData && userData.isEmailVerified) { + return true; + } + return false; + } + + async getEcoUserName(userEmail: string): Promise { + const pattern = { cmd: 'get-user-by-mail' }; + const payload = { email: userEmail }; + + const userData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return userData; + } + + // eslint-disable-next-line camelcase + async removeEndorsementTransactionFields(transactionObject: endorsement_transaction): Promise { + const transaction = transactionObject; + + // To return selective response + delete transaction.requestPayload; + delete transaction.responsePayload; + delete transaction.lastChangedDateTime; + delete transaction.lastChangedBy; + delete transaction.deletedAt; + delete transaction.requestBody; + delete transaction.resourceId; + } + + /** + * + * @param IRequestSchemaEndorsement + * @returns + */ + + async requestSchemaEndorsement( + requestSchemaPayload: IRequestSchemaEndorsement, + user: user, + orgId: string, + ecosystemId: string + ): Promise { + try { + const platformConfig = await this.ecosystemRepository.getPlatformConfigDetails(); + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } + + const agentDetails = await this.ecosystemRepository.getAgentDetails(orgId); + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.schema.error.agentDetailsNotFound, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + const { schemaPayload, type, endorse } = requestSchemaPayload; + + let isSchemaExist; + if (type === SchemaTypeEnum.INDY) { + const indySchema = schemaPayload as IRequestIndySchemaEndorsement; + const { schemaName, schemaVersion } = indySchema; + isSchemaExist = await this._schemaExist(schemaName, schemaVersion); + this.logger.log(`isSchemaExist ::: ${JSON.stringify(isSchemaExist.length)}`); + + if (0 !== isSchemaExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); + } + + if (0 === schemaName.length) { + throw new BadRequestException(ResponseMessages.schema.error.nameNotEmpty); + } + + if (0 === schemaVersion.length) { + throw new BadRequestException(ResponseMessages.schema.error.versionNotEmpty); + } + + const schemaVersionIndexOf = -1; + + if (isNaN(parseFloat(schemaVersion)) || schemaVersion.toString().indexOf('.') === schemaVersionIndexOf) { + throw new NotAcceptableException(ResponseMessages.schema.error.invalidVersion); + } + return await this.requestIndySchemaEndorsement( + schemaPayload as IRequestIndySchemaEndorsement, + orgId, + ecosystemId, + user.id, + endorse + ); + } else if (type === SchemaTypeEnum.JSON) { + const { schemaName, schemaType } = schemaPayload as IRequestW3CSchemaEndorsement; + const ledgerAndNetworkDetails = await checkDidLedgerAndNetwork(schemaType, agentDetails.orgDid); + if (!ledgerAndNetworkDetails) { + throw new BadRequestException(ResponseMessages.schema.error.orgDidAndSchemaType, { + cause: new Error(), + description: ResponseMessages.errorMessages.badRequest + }); + } + isSchemaExist = await this._schemaExist(schemaName); + + if (0 !== isSchemaExist.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaNameAlreadyExist); + } + + return await this.requestW3CSchemaEndorsement( + schemaPayload as IRequestW3CSchemaEndorsement, + orgId, + ecosystemId, + user.id + ); + } + } catch (error) { + this.logger.error(`In request schema endorsement : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error?.error); + } + } + + async requestIndySchemaEndorsement( + requestSchemaPayload: IRequestIndySchemaEndorsement, + orgId: string, + ecosystemId: string, + userId: string, + endorse: boolean + ): Promise { + try { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [schemaRequestExist, ecosystemOrgAgentDetails, ecosystemLeadAgentDetails, getEcosystemOrgDetailsByOrgId] = + await Promise.all([ + this.ecosystemRepository.findRecordsByNameAndVersion( + requestSchemaPayload?.schemaName, + requestSchemaPayload?.schemaVersion + ), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getAgentDetails(getEcosystemLeadDetails.orgId), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(orgId, ecosystemId) + ]); + + const existSchema = + schemaRequestExist?.filter( + (schema) => schema.status === endorsementTransactionStatus.REQUESTED || + schema.status === endorsementTransactionStatus.SIGNED || + schema.status === endorsementTransactionStatus.SUBMITED + ) ?? []; + + if (0 < existSchema.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist); + } + + if (!ecosystemOrgAgentDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.notFound); + } + + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } + + if (!ecosystemLeadAgentDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.leadNotFound); + } + + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); + } + + const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemOrgAgentDetails.orgAgentTypeId); + const url = await this.getAgentUrl( + orgAgentType, + ecosystemOrgAgentDetails.agentEndPoint, + endorsementTransactionType.SCHEMA, + ecosystemOrgAgentDetails.tenantId + ); + const attributeArray = requestSchemaPayload.attributes.map((item) => item.attributeName); + + const schemaTransactionPayload = { + endorserDid: ecosystemLeadAgentDetails.orgDid.trim(), + endorse, + attributes: attributeArray, + version: String(requestSchemaPayload.schemaVersion), + name: requestSchemaPayload.schemaName.trim(), + issuerId: ecosystemOrgAgentDetails.orgDid.trim() + }; + + const schemaTransactionRequest: SchemaMessage = await this._requestSchemaEndorsement( + schemaTransactionPayload, + url, + orgId + ); + + const schemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: ecosystemOrgAgentDetails.orgDid, + requestPayload: schemaTransactionRequest.message.schemaState.schemaRequest, + status: endorsementTransactionStatus.REQUESTED, + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id, + userId + }; + + if ('failed' === schemaTransactionRequest.message.schemaState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestSchemaTransaction); + } + + const storeTransaction = await this.ecosystemRepository.storeTransactionRequest( + schemaTransactionResponse, + requestSchemaPayload, + endorsementTransactionType.SCHEMA + ); + + await this.removeEndorsementTransactionFields(storeTransaction); + + return storeTransaction; + } catch (error) { + this.logger.error(`In request indy schema endorsement : ${JSON.stringify(error)}`); + throw new RpcException(error ? error.response : error); + } + } + + async requestW3CSchemaEndorsement( + requestSchemaPayload: IRequestW3CSchemaEndorsement, + orgId: string, + ecosystemId: string, + userId: string + ): Promise { + try { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [ + schemaRequestExist, + ecosystemMemberOrgAgentDetails, + ecosystemLeadAgentDetails, + getEcosystemOrgDetailsByOrgId + ] = await Promise.all([ + this.ecosystemRepository.findSchemaRecordsBySchemaName(requestSchemaPayload?.schemaName), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getAgentDetails(getEcosystemLeadDetails.orgId), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(orgId, ecosystemId) + ]); + + const existSchema = + schemaRequestExist?.filter( + (schema) => schema.status === endorsementTransactionStatus.REQUESTED || + schema.status === endorsementTransactionStatus.SUBMITED + ) ?? []; + + if (0 < existSchema.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.schemaNameAlreadyExist); + } + + if (!ecosystemMemberOrgAgentDetails) { + throw new InternalServerErrorException('Error in fetching agent details'); + } + + const w3cSchemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: ecosystemMemberOrgAgentDetails.orgDid, + requestPayload: JSON.stringify(requestSchemaPayload), + status: endorsementTransactionStatus.REQUESTED, + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id, + userId + }; + + const storeTransaction = await this.ecosystemRepository.storeTransactionRequest( + w3cSchemaTransactionResponse, + requestSchemaPayload, + endorsementTransactionType.W3C_SCHEMA + ); + await this.removeEndorsementTransactionFields(storeTransaction); + + return storeTransaction; + } catch (error) { + this.logger.error(`In request w3c schema endorsement: ${JSON.stringify(error)}`); + throw error; + } + } + + async _schemaExist(schemaName: string, version?: string): Promise { + const pattern = { cmd: 'schema-exist' }; + const payload = { schemaName, version }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + } + } + + async requestCredDeffEndorsement( + requestCredDefPayload: RequestCredDeffEndorsement, + orgId: string, + ecosystemId: string + ): Promise { + try { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const [ + credDefRequestExist, + ecosystemMemberDetails, + platformConfig, + ecosystemLeadAgentDetails, + getEcosystemOrgDetailsByOrgId + ] = await Promise.all([ + this.ecosystemRepository.findRecordsByCredDefTag(requestCredDefPayload?.tag), + this.ecosystemRepository.getAgentDetails(orgId), + this.ecosystemRepository.getPlatformConfigDetails(), + this.ecosystemRepository.getAgentDetails(getEcosystemLeadDetails.orgId), + this.ecosystemRepository.getEcosystemOrgDetailsbyId(orgId, ecosystemId) + ]); + + const existsCredDef = + credDefRequestExist?.filter( + (tag) => tag.status === endorsementTransactionStatus.REQUESTED || + tag.status === endorsementTransactionStatus.SIGNED || + tag.status === endorsementTransactionStatus.SUBMITED + ) ?? []; + + if (0 < existsCredDef.length) { + throw new ConflictException(ResponseMessages.ecosystem.error.credDefAlreadyExist); + } + + if (!ecosystemMemberDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.notFound); + } + + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } + + if (!getEcosystemLeadDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } + + if (!ecosystemLeadAgentDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } + + if (!getEcosystemOrgDetailsByOrgId) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemOrgNotFound); + } + + let requestCredDefBody; + const credDefData = credDefRequestExist?.filter((tag) => tag.status === endorsementTransactionStatus.DECLINED); + if (0 < credDefData.length) { + let schemaTransactionResponse; + credDefRequestExist.forEach((tag) => { + requestCredDefBody = tag.requestBody; + schemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: ecosystemMemberDetails.orgDid, + requestPayload: tag.requestPayload, + status: endorsementTransactionStatus.REQUESTED, + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id, + userId: requestCredDefPayload.userId + }; + }); + const storeTransaction = await this.ecosystemRepository.storeTransactionRequest( + schemaTransactionResponse, + requestCredDefBody, + endorsementTransactionType.CREDENTIAL_DEFINITION + ); + + // To return selective response + await this.removeEndorsementTransactionFields(storeTransaction); + + await new Promise((resolve) => setTimeout(resolve, 5000)); + return storeTransaction; + } else { + const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemMemberDetails.orgAgentTypeId); + const url = await this.getAgentUrl( + orgAgentType, + ecosystemMemberDetails.agentEndPoint, + endorsementTransactionType.CREDENTIAL_DEFINITION, + ecosystemMemberDetails.tenantId + ); + const credDefTransactionPayload = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + endorse: requestCredDefPayload.endorse, + tag: requestCredDefPayload.tag, + schemaId: requestCredDefPayload.schemaId, + issuerId: ecosystemMemberDetails.orgDid + }; + + const credDefTransactionRequest: CredDefMessage = await this._requestCredDeffEndorsement( + credDefTransactionPayload, + url, + orgId + ); + + if ('failed' === credDefTransactionRequest.message.credentialDefinitionState.state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.requestCredDefTransaction); + } + + const requestBody = credDefTransactionRequest.message.credentialDefinitionState.credentialDefinition; + + if (!requestBody) { + throw new NotFoundException(ResponseMessages.ecosystem.error.credentialDefinitionNotFound); + } + + requestCredDefPayload['credentialDefinition'] = requestBody; + const schemaTransactionResponse = { + endorserDid: ecosystemLeadAgentDetails.orgDid, + authorDid: ecosystemMemberDetails.orgDid, + requestPayload: credDefTransactionRequest.message.credentialDefinitionState.credentialDefinitionRequest, + status: endorsementTransactionStatus.REQUESTED, + ecosystemOrgId: getEcosystemOrgDetailsByOrgId.id, + userId: requestCredDefPayload.userId + }; + + const storeTransaction = await this.ecosystemRepository.storeTransactionRequest( + schemaTransactionResponse, + requestCredDefPayload, + endorsementTransactionType.CREDENTIAL_DEFINITION + ); + + // To return selective response + await this.removeEndorsementTransactionFields(storeTransaction); + + return storeTransaction; + } + } catch (error) { + this.logger.error(`In request cred-def endorsement: ${JSON.stringify(error)}`); + const errorObj = error?.status?.message?.error; + if (errorObj) { + throw new RpcException({ + message: errorObj?.reason ? errorObj?.reason : errorObj, + statusCode: error?.status?.code + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + } + + async getInvitationsByEcosystemId(payload: FetchInvitationsPayload): Promise { + try { + const { ecosystemId, pageNumber, pageSize, search } = payload; + const ecosystemInvitations = await this.ecosystemRepository.getInvitationsByEcosystemId( + ecosystemId, + pageNumber, + pageSize, + search + ); + return ecosystemInvitations; + } catch (error) { + this.logger.error(`In getInvitationsByEcosystemId : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async _requestSchemaEndorsement(requestSchemaPayload: object, url: string, orgId: string): Promise { + const pattern = { cmd: 'agent-schema-endorsement-request' }; + const payload = { requestSchemaPayload, url, orgId }; + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return { message }; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw error; + } + } + + async _requestCredDeffEndorsement(requestSchemaPayload: object, url: string, orgId: string): Promise { + const pattern = { cmd: 'agent-credDef-endorsement-request' }; + const payload = { requestSchemaPayload, url, orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return { message }; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + } + } + + async signTransaction(endorsementId: string, ecosystemId: string): Promise { + try { + const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + + const checkEndorsementRequestIsExists = await this.ecosystemRepository.getTransactionDetailsByEndorsementId( + endorsementId + ); + + if (checkEndorsementRequestIsExists?.status === endorsementTransactionStatus.SIGNED) { + throw new ConflictException(ResponseMessages.ecosystem.error.transactionAlreadySigned); + } else if (checkEndorsementRequestIsExists?.status === endorsementTransactionStatus.SUBMITED) { + throw new ConflictException(ResponseMessages.ecosystem.error.transactionSubmitted); + } + + if (!ecosystemDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotFound); + } + + const [endorsementTransactionPayload, ecosystemLeadDetails, platformConfig] = await Promise.all([ + this.ecosystemRepository.getEndorsementTransactionById(endorsementId, endorsementTransactionStatus.REQUESTED), + this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId), + this.ecosystemRepository.getPlatformConfigDetails() + ]); + + if ( + endorsementTransactionPayload && + endorsementTransactionPayload?.type === endorsementTransactionType.W3C_SCHEMA + ) { + throw new BadRequestException(ResponseMessages.ecosystem.error.signTransactionNotApplicable); + } + + if (!endorsementTransactionPayload) { + throw new NotFoundException(ResponseMessages.ecosystem.error.invalidTransaction); + } + + if (!ecosystemLeadDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } + + if (!platformConfig) { + throw new NotFoundException(ResponseMessages.ecosystem.error.platformConfigNotFound); + } + + const ecosystemLeadAgentDetails = await this.ecosystemRepository.getAgentDetails(ecosystemLeadDetails.orgId); + + if (!ecosystemLeadAgentDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.leadNotFound); + } + + const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemLeadAgentDetails?.orgAgentTypeId); + const url = await this.getAgentUrl( + orgAgentType, + ecosystemLeadAgentDetails.agentEndPoint, + endorsementTransactionType.SIGN, + ecosystemLeadAgentDetails?.tenantId + ); + + const jsonString = endorsementTransactionPayload.requestPayload.toString(); + const payload = { + transaction: jsonString, + endorserDid: endorsementTransactionPayload.endorserDid + }; + const schemaTransactionRequest: SignedTransactionMessage = await this._signTransaction( + payload, + url, + ecosystemLeadDetails.orgId + ); + + if (!schemaTransactionRequest) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.signRequestError); + } + + const updateSignedTransaction = await this.ecosystemRepository.updateTransactionDetails( + endorsementId, + schemaTransactionRequest.message.signedTransaction + ); + + if (!updateSignedTransaction) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateTransactionError); + } + + if (updateSignedTransaction && true === ecosystemDetails.autoEndorsement) { + const submitTxn = await this.submitTransaction({ + endorsementId, + ecosystemId, + ecosystemLeadAgentEndPoint: ecosystemLeadAgentDetails.agentEndPoint, + orgId: ecosystemLeadDetails.orgId + }); + if (!submitTxn) { + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.REQUESTED); + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + const finalResponse = { + autoEndorsement: ecosystemDetails.autoEndorsement, + submitTxn + }; + return finalResponse; + } + + await this.removeEndorsementTransactionFields(updateSignedTransaction); + return updateSignedTransaction; + } catch (error) { + this.logger.error(`In sign transaction: ${JSON.stringify(error)}`); + const errorObject = error?.error; + if (errorObject) { + throw new RpcException({ + statusCode: errorObject?.error?.error?.message?.statusCode, + message: 'Error in transaction process', + error: errorObject?.error?.error?.message?.error?.message + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + } + + /** + * + * @returns Ecosystem members list + */ + + async getEcosystemMembers(payload: EcosystemMembersPayload): Promise { + try { + const { ecosystemId, pageNumber, pageSize, search, sortBy, sortField } = payload; + const getEcosystemMember = await this.ecosystemRepository.findEcosystemMembers( + ecosystemId, + pageNumber, + pageSize, + search, + sortBy, + sortField + ); + + const ecosystemMemberResponse = { + totalItems: getEcosystemMember[1], + hasNextPage: payload.pageSize * payload.pageNumber < getEcosystemMember[1], + hasPreviousPage: 1 < payload.pageNumber, + nextPage: Number(payload.pageNumber) + 1, + previousPage: payload.pageNumber - 1, + lastPage: Math.ceil(getEcosystemMember[1] / payload.pageSize), + data: getEcosystemMember[0] + }; + + return ecosystemMemberResponse; + } catch (error) { + this.logger.error(`In getEcosystemMembers: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async deleteEcosystemInvitations(invitationId: string): Promise { + try { + return await this.ecosystemRepository.deleteInvitations(invitationId); + } catch (error) { + this.logger.error(`In error deleteEcosystemInvitation: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + /** + * Description: Store shortening URL + * @param signEndorsementPayload + * @param url + * @returns sign message + */ + async _signTransaction(signEndorsementPayload: object, url: string, orgId: string): Promise { + const pattern = { cmd: 'agent-sign-transaction' }; + const payload = { signEndorsementPayload, url, orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return { message }; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + } + } + + // eslint-disable-next-line camelcase + async getEcosystemMemberDetails(endorsementTransactionPayload): Promise { + const orgId = endorsementTransactionPayload.ecosystemOrgs.orgId; + return this.ecosystemRepository.getAgentDetails(orgId); + } + + // eslint-disable-next-line camelcase + async getEcosystemLeadAgentDetails(ecosystemId: string): Promise { + const getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + return this.ecosystemRepository.getAgentDetails(getEcosystemLeadDetails.orgId); + } + + // eslint-disable-next-line camelcase + async getPlatformConfig(): Promise { + return this.ecosystemRepository.getPlatformConfigDetails(); + } + + async submitTransactionPayload( + endorsementTransactionPayload, + ecosystemMemberDetails, + ecosystemLeadAgentDetails + ): Promise { + const parsedRequestPayload = JSON.parse(endorsementTransactionPayload.responsePayload); + const jsonString = endorsementTransactionPayload.responsePayload.toString(); + const payload: submitTransactionPayload = { + endorsedTransaction: jsonString, + endorserDid: ecosystemLeadAgentDetails.orgDid + }; + + switch (endorsementTransactionPayload.type) { + case endorsementTransactionType.SCHEMA: + payload.schema = { + attributes: parsedRequestPayload.operation.data.attr_names, + version: parsedRequestPayload.operation.data.version, + name: parsedRequestPayload.operation.data.name, + issuerId: ecosystemMemberDetails.orgDid + }; + break; + + case endorsementTransactionType.CREDENTIAL_DEFINITION: + payload.credentialDefinition = { + tag: parsedRequestPayload.operation.tag, + issuerId: ecosystemMemberDetails.orgDid, + schemaId: endorsementTransactionPayload.requestBody.credentialDefinition['schemaId'], + type: endorsementTransactionPayload.requestBody.credentialDefinition['type'], + value: endorsementTransactionPayload.requestBody.credentialDefinition['value'] + }; + break; + default: + throw new Error('Unsupported endorsement transaction type'); + } + + return payload; + } + + async handleSchemaSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ): Promise { + const regex = /[^:]+$/; + const match = ecosystemMemberDetails.orgDid.match(regex); + let extractedDidValue; + + if (match) { + // eslint-disable-next-line prefer-destructuring + extractedDidValue = match[0]; + } + const saveSchemaPayload: SaveSchema = { + name: endorsementTransactionPayload.requestBody['schemaName'], + version: endorsementTransactionPayload.requestBody['schemaVersion'], + attributes: JSON.stringify(endorsementTransactionPayload.requestBody['attributes']), + schemaLedgerId: submitTransactionRequest['message'].schemaId, + issuerId: ecosystemMemberDetails.orgDid, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + publisherDid: extractedDidValue, + orgId: endorsementTransactionPayload.ecosystemOrgs.orgId, + ledgerId: ecosystemMemberDetails.ledgerId, + type: SchemaType.INDY + }; + const saveSchemaDetails = await this.ecosystemRepository.saveSchema(saveSchemaPayload); + if (!saveSchemaDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveSchema); + } + return saveSchemaDetails; + } + + // eslint-disable-next-line camelcase + async handleCredDefSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + // eslint-disable-next-line camelcase + ): Promise { + const schemaDetails = await this.ecosystemRepository.getSchemaDetailsById( + endorsementTransactionPayload.requestBody['schemaId'] + ); + + if (!schemaDetails) { + throw new NotFoundException(ResponseMessages.ecosystem.error.schemaNotFound); + } + + const saveCredentialDefinition: saveCredDef = { + schemaLedgerId: endorsementTransactionPayload.requestBody['schemaId'], + tag: endorsementTransactionPayload.requestBody['tag'], + credentialDefinitionId: submitTransactionRequest['message'].credentialDefinitionId, + revocable: false, + createdBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + lastChangedBy: endorsementTransactionPayload.ecosystemOrgs.orgId, + orgId: ecosystemMemberDetails.orgId, + schemaId: schemaDetails.id + }; + + const saveCredDefDetails = await this.ecosystemRepository.saveCredDef(saveCredentialDefinition); + if (!saveCredDefDetails) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.saveCredDef); + } + return saveCredDefDetails; + } + + async updateTransactionStatus(endorsementId: string): Promise { + return this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SUBMITED); + } + + async submitTransaction(transactionPayload: ITransactionData): Promise<{txnPayload: object, responseMessage: string}> { + try { + let txnPayload; + + const { endorsementId, orgId, ecosystemId } = transactionPayload; + + const ecosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + + const endorsementPayload = await this.ecosystemRepository.getTransactionDetailsByEndorsementId(endorsementId); + + const orgIdsToCheck = [ecosystemLeadDetails?.orgId, endorsementPayload?.['ecosystemOrgs']?.orgId]; + + if (!orgIdsToCheck.includes(orgId)) { + throw new ForbiddenException(ResponseMessages.organisation.error.orgNotMatch); + } + + if (endorsementTransactionStatus.SUBMITED === endorsementPayload?.status) { + throw new ConflictException(ResponseMessages.ecosystem.error.transactionSubmitted); + } + + const parsedRequestPayload = JSON.parse(endorsementPayload?.requestPayload); + + const responseMessage = LedgerLessConstant.NO_LEDGER === parsedRequestPayload?.schemaType + ? ResponseMessages.ecosystem.success.submitNoLedgerSchema + : ResponseMessages.ecosystem.success.submit; + + if (endorsementPayload?.type === endorsementTransactionType.W3C_SCHEMA) { + txnPayload = await this.submitW3CTransaction(transactionPayload); + } else { + txnPayload = await this.submitIndyTransaction(transactionPayload); + } + + return { txnPayload, responseMessage }; + } catch (error) { + this.logger.error(`In submit transaction: ${JSON.stringify(error)}`); + if (error?.error) { + throw new RpcException({ + statusCode: error?.error?.statusCode, + message: error?.error?.message, + error: error?.error?.error + }); + } else { + this.handleException(error); + } + } + } + + async submitIndyTransaction(transactionPayload: ITransactionData): Promise { + try { + const { endorsementId, ecosystemId, ecosystemLeadAgentEndPoint, orgId } = transactionPayload; + + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById( + endorsementId, + endorsementTransactionStatus.SIGNED + ); + + if (!endorsementTransactionPayload) { + throw new BadRequestException(ResponseMessages.ecosystem.error.transactionNotSigned); + } + + const ecosystemMemberDetails = await this.getEcosystemMemberDetails(endorsementTransactionPayload); + const ecosystemLeadAgentDetails = await this.getEcosystemLeadAgentDetails(ecosystemId); + const agentEndPoint = ecosystemLeadAgentEndPoint + ? ecosystemLeadAgentEndPoint + : ecosystemMemberDetails.agentEndPoint; + + const orgAgentType = await this.ecosystemRepository.getOrgAgentType(ecosystemMemberDetails?.orgAgentTypeId); + const url = await this.getAgentUrl( + orgAgentType, + agentEndPoint, + endorsementTransactionType.SUBMIT, + ecosystemMemberDetails?.tenantId + ); + const payload = await this.submitTransactionPayload( + endorsementTransactionPayload, + ecosystemMemberDetails, + ecosystemLeadAgentDetails + ); + + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + const isSchemaExists = await this.ecosystemRepository.schemaExist(payload.schema.name, payload.schema.version); + + if (0 !== isSchemaExists.length) { + this.logger.error(ResponseMessages.ecosystem.error.schemaAlreadyExist); + throw new ConflictException(ResponseMessages.ecosystem.error.schemaAlreadyExist, { + cause: new Error(), + description: ResponseMessages.errorMessages.conflict + }); + } + } + + const submitTransactionRequest = await this._submitTransaction(payload, url, orgId); + + if ('failed' === submitTransactionRequest['message'].state) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + + await this.updateTransactionStatus(endorsementId); + + if (endorsementTransactionPayload.type === endorsementTransactionType.SCHEMA) { + const updateSchemaId = await this._updateResourceId( + endorsementId, + endorsementTransactionType.SCHEMA, + submitTransactionRequest + ); + + if (!updateSchemaId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateSchemaId); + } + + const response = this.handleSchemaSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ); + + return response; + } else if (endorsementTransactionPayload.type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + if ('undefined' === submitTransactionRequest['message'].credentialDefinitionId.split(':')[3]) { + const ecosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + if (true === ecosystemDetails.autoEndorsement) { + await this.ecosystemRepository.updateTransactionStatus( + endorsementId, + endorsementTransactionStatus.REQUESTED + ); + } else { + await this.ecosystemRepository.updateTransactionStatus(endorsementId, endorsementTransactionStatus.SIGNED); + } + + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.sumbitTransaction); + } + const updateCredDefId = await this._updateResourceId( + endorsementId, + endorsementTransactionType.CREDENTIAL_DEFINITION, + submitTransactionRequest + ); + + if (!updateCredDefId) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.updateCredDefId); + } + return this.handleCredDefSubmission( + endorsementTransactionPayload, + ecosystemMemberDetails, + submitTransactionRequest + ); + } + } catch (error) { + this.logger.error(`In submit Indy transaction: ${JSON.stringify(error)}`); + this.handleException(error); + } + } + + async submitW3CTransaction(transactionPayload: ITransactionData): Promise { + try { + const { endorsementId } = transactionPayload; + const endorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionById( + endorsementId, + endorsementTransactionStatus.REQUESTED + ); + + if (!endorsementTransactionPayload) { + throw new BadRequestException(ResponseMessages.ecosystem.error.transactionNotRequested); + } + + const w3cEndorsementTransactionPayload = await this.ecosystemRepository.getEndorsementTransactionByIdAndType( + endorsementId, + endorsementTransactionType.W3C_SCHEMA + ); + + const schemaPayload = { + schemaDetails: { + type: SchemaTypeEnum.JSON, + schemaPayload: { + schemaName: w3cEndorsementTransactionPayload?.requestBody?.['schemaName'], + attributes: w3cEndorsementTransactionPayload?.requestBody?.['attributes'], + schemaType: w3cEndorsementTransactionPayload?.requestBody?.['schemaType'], + description: w3cEndorsementTransactionPayload?.requestBody?.['description'] + } + }, + orgId: w3cEndorsementTransactionPayload?.['ecosystemOrgs']?.orgId, + user: transactionPayload?.user + }; + + const w3cSchemaResponse = await this._createW3CSchema(schemaPayload); + + const resourceId = w3cSchemaResponse?.['schemaUrl']; + + await this.ecosystemRepository.updateResourse(endorsementId, resourceId); + + await this.updateTransactionStatus(endorsementId); + + return w3cSchemaResponse; + } catch (error) { + this.logger.error(`In submit w3c transaction: ${JSON.stringify(error)}`); + this.handleException(error); + } + } + + private handleException(error): void { + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { + throw new RpcException({ + message: error?.status?.message?.error?.reason + ? error?.status?.message?.error?.reason + : error?.status?.message?.error, + statusCode: error?.status?.code + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } + + async _createW3CSchema(payload: IschemaPayload): Promise { + const pattern = { cmd: 'create-schema' }; + + const w3cSchemaData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return w3cSchemaData; + } + + /** + * Description: Store shortening URL + * @param signEndorsementPayload + * @param url + * @returns sign message + */ + async _submitTransaction(submitEndorsementPayload: object, url: string, orgId: string): Promise { + const pattern = { cmd: 'agent-submit-transaction' }; + const payload = { submitEndorsementPayload, url, orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return { message }; + } catch (error) { + this.logger.error(` agent-submit-transaction catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + message: error.message, + error: error.error + }, + error.status + ); + } + } + + async _updateResourceId( + endorsementId: string, + transactionType: endorsementTransactionType, + transactionDetails: object + ): Promise { + try { + // eslint-disable-next-line prefer-destructuring + const message = transactionDetails['message']; + if (!message) { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidMessage); + } + + const resourceId = + message[transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId']; + + if (!resourceId) { + throw new Error( + `${ResponseMessages.ecosystem.error.invalidTransactionMessage} Missing "${ + transactionType === endorsementTransactionType.SCHEMA ? 'schemaId' : 'credentialDefinitionId' + }" property.` + ); + } + + return await this.ecosystemRepository.updateResourse(endorsementId, resourceId); + } catch (error) { + this.logger.error(`Error updating resource ID: ${JSON.stringify(error)}`); + } + } + + async getAgentUrl(orgAgentTypeId: string, agentEndPoint: string, type: string, tenantId?: string): Promise { + try { + let url; + + if (orgAgentTypeId === OrgAgentType.DEDICATED) { + if (type === endorsementTransactionType.SCHEMA) { + url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_SCHEMA}`; + } else if (type === endorsementTransactionType.W3C_SCHEMA) { + url = `${agentEndPoint}${CommonConstants.DEDICATED_CREATE_POLYGON_W3C_SCHEMA}`; + } else if (type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + url = `${agentEndPoint}${CommonConstants.URL_SCHM_CREATE_CRED_DEF}`; + } else if (type === endorsementTransactionType.SIGN) { + url = `${agentEndPoint}${CommonConstants.SIGN_TRANSACTION}`; + } else if (type === endorsementTransactionType.SUBMIT) { + url = `${agentEndPoint}${CommonConstants.SUBMIT_TRANSACTION}`; + } + } else if (orgAgentTypeId === OrgAgentType.SHARED) { + // TODO + if (tenantId !== undefined) { + if (type === endorsementTransactionType.SCHEMA) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SCHEMA}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.W3C_SCHEMA) { + url = `${agentEndPoint}${CommonConstants.SHARED_CREATE_POLYGON_W3C_SCHEMA}${tenantId}`; + } else if (type === endorsementTransactionType.CREDENTIAL_DEFINITION) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_CRED_DEF}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.SIGN) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SIGN}`.replace('#', tenantId); + } else if (type === endorsementTransactionType.SUBMIT) { + url = `${agentEndPoint}${CommonConstants.TRANSACTION_MULTITENANT_SUMBIT}`.replace('#', tenantId); + } else { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidAgentUrl); + } + } else { + throw new InternalServerErrorException(ResponseMessages.ecosystem.error.invalidAgentUrl); + } + } else { + throw new NotFoundException(ResponseMessages.connection.error.agentUrlNotFound); + } + + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } + } + + async fetchEcosystemOrg(payload: { ecosystemId: string; orgId: string }): Promise { + const isEcosystemEnabled = await this.checkEcosystemEnableFlag(); + + if (!isEcosystemEnabled) { + throw new ForbiddenException(ResponseMessages.ecosystem.error.ecosystemNotEnabled); + } + + return this.ecosystemRepository.fetchEcosystemOrg(payload); + } + + /** + * + * @returns Returns ecosystem flag from settings + */ + async checkEcosystemEnableFlag(): Promise { + const ecosystemDetails = await this.prisma.ecosystem_config.findFirst({ + where: { + key: 'enableEcosystem' + } + }); + + if ('true' === ecosystemDetails.value) { + return true; + } + + return false; + } + + async getEndorsementTransactions(payload: GetEndorsementsPayload): Promise { + const { ecosystemId, orgId, pageNumber, pageSize, search, type } = payload; + try { + const queryEcoOrgs = { + ecosystemId, + orgId + }; + + const query = { + ecosystemOrgs: { + ecosystemId + }, + OR: [ + { status: { contains: search, mode: 'insensitive' } }, + { authorDid: { contains: search, mode: 'insensitive' } } + ] + }; + + const ecosystemOrgData = await this.ecosystemRepository.fetchEcosystemOrg(queryEcoOrgs); + + if (ecosystemOrgData['ecosystemRole']['name'] !== EcosystemRoles.ECOSYSTEM_LEAD) { + query.ecosystemOrgs['orgId'] = orgId; + } + + if (type) { + query['type'] = type; + } + + return await this.ecosystemRepository.getEndorsementsWithPagination(query, pageNumber, pageSize); + } catch (error) { + this.logger.error(`In error getEndorsementTransactions: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async getAllEcosystemSchemas(ecosystemSchemas: GetAllSchemaList): Promise { + try { + const response = await this.ecosystemRepository.getAllEcosystemSchemasDetails(ecosystemSchemas); + const schemasDetails = response?.schemasResult.map((schemaAttributeItem) => { + const attributes = JSON.parse(schemaAttributeItem.attributes); + return { ...schemaAttributeItem, attributes }; + }); + + const schemasResponse: ISchemasResponse = { + schemasCount: response.schemasCount, + schemasResult: schemasDetails + }; + + return schemasResponse; + } catch (error) { + this.logger.error(`In error fetching all ecosystem schemas: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * @returns EndorsementTransaction Status message + */ + + async autoSignAndSubmitTransaction(): Promise { + try { + return await this.ecosystemRepository.updateAutoSignAndSubmitTransaction(); + } catch (error) { + this.logger.error(`error in decline endorsement request: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + /** + * + * @param ecosystemId + * @param endorsementId + * @param orgId + * @returns EndorsementTransactionRequest Status message + */ + + async declineEndorsementRequestByLead(ecosystemId: string, endorsementId: string): Promise { + try { + const declineResponse = await this.ecosystemRepository.updateEndorsementRequestStatus(ecosystemId, endorsementId); + + // To return selective response + this.removeEndorsementTransactionFields(declineResponse); + + return declineResponse; + } catch (error) { + this.logger.error(`error in decline endorsement request: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async _getOrgAgentApiKey(orgId: string): Promise { + const pattern = { cmd: 'get-org-agent-api-key' }; + const payload = { orgId }; + + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const message = await this.ecosystemServiceProxy.send(pattern, payload).toPromise(); + return message; + } catch (error) { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + } + } + + async sendMailToEcosystemMembers(email: string, orgName: string, ecosystemName: string): Promise { + const platformConfigData = await this.prisma.platform_config.findMany(); + + const urlEmailTemplate = new DeleteEcosystemMemberTemplate(); + const emailData = new EmailDto(); + emailData.emailFrom = platformConfigData[0].emailFrom; + emailData.emailTo = email; + emailData.emailSubject = `Removal of participation of “${orgName}” from ${ecosystemName}`; + + emailData.emailHtml = urlEmailTemplate.sendDeleteMemberEmailTemplate(email, orgName, ecosystemName); + + const isEmailSent = await sendEmail(emailData); + + return isEmailSent; + } + + async _getOrgData(orgId: string): Promise { + const pattern = { cmd: 'get-organization-details' }; + const payload = { orgId }; + const orgData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return orgData; + } + + async _getUsersDetails(userId: string): Promise { + const pattern = { cmd: 'get-user-details-by-userId' }; + const payload = { userId }; + const userData = await this.ecosystemServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); + return userData; + } + + async deleteOrgFromEcosystem(orgId: string, userDetails: user): Promise { + try { + const getEcosystems = await this.ecosystemRepository.getEcosystemsByOrgId(orgId); + + const ecosystemLeadRoleOrgs = []; + const ecosystemMemberRoleOrgs = []; + + getEcosystems.forEach((ecosystem) => { + ecosystem.ecosystemOrgs.forEach((org) => { + if (EcosystemRoles.ECOSYSTEM_LEAD === org?.ecosystemRole?.name) { + ecosystemLeadRoleOrgs.push(org); + } else if (EcosystemRoles.ECOSYSTEM_MEMBER === org?.ecosystemRole?.name) { + ecosystemMemberRoleOrgs.push(org); + } + }); + }); + + const getEcosystemLeadRoleOrgsIds = ecosystemLeadRoleOrgs?.map((org) => org?.orgId); + const getEcosystemMemberRoleOrgIds = ecosystemMemberRoleOrgs?.map((org) => org?.orgId); + + if (getEcosystemLeadRoleOrgsIds?.includes(orgId)) { + throw new BadRequestException(ResponseMessages.ecosystem.error.notAbleToDeleteEcosystem); + } + + let ecosystemId; + let getEcosystemDetails; + let getEcosystemLeadDetails; + + getEcosystems.forEach((ecosystem) => { + ecosystem.ecosystemOrgs.forEach((org) => { + if (org.ecosystemRole && org.ecosystemRole.name === EcosystemRoles.ECOSYSTEM_LEAD) { + ecosystemId = org?.ecosystemId; + } + }); + }); + + if (ecosystemId) { + getEcosystemDetails = await this.ecosystemRepository.getEcosystemDetails(ecosystemId); + getEcosystemLeadDetails = await this.ecosystemRepository.getEcosystemLeadDetails(ecosystemId); + } else { + throw new NotFoundException(ResponseMessages.ecosystem.error.ecosystemNotExists); + } + + const getLeadUserId = getEcosystemLeadDetails?.createdBy; + + const getLeadEmailId = await this._getUsersDetails(getLeadUserId); + + const getOrgName = await this._getOrgData(orgId); + + let deleteEcosystems; + + if (getEcosystemMemberRoleOrgIds?.includes(orgId)) { + deleteEcosystems = await this.ecosystemRepository.deleteMemberOrgFromEcosystem(orgId); + await this.ecosystemRepository.deleteEcosystemInvitations(orgId); + await this.sendMailToEcosystemMembers(getLeadEmailId, getOrgName?.['name'], getEcosystemDetails?.name); + } + + const ecosystemDataCount = { + deletedEndorsementTransactionsCount: deleteEcosystems?.deleteEndorsementTransactions?.count + }; + + const deletedEcosystemsData = { + deletedEcosystemCount: deleteEcosystems?.deletedEcosystemOrgs?.count, + deletedEcosystemDataCount: ecosystemDataCount + }; + + await this.userActivityRepository._orgDeletedActivity( + orgId, + userDetails, + deletedEcosystemsData, + RecordType.ECOSYSTEM_MEMBER + ); + return deleteEcosystems; + } catch (error) { + this.logger.error(`In delete ecosystems: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } +} \ No newline at end of file diff --git a/apps/ecosystem/src/main.ts b/apps/ecosystem/src/main.ts new file mode 100644 index 000000000..1ca9c8acf --- /dev/null +++ b/apps/ecosystem/src/main.ts @@ -0,0 +1,23 @@ +import { NestFactory } from '@nestjs/core'; +import { EcosystemModule } from './ecosystem.module'; +import { HttpExceptionFilter } from 'libs/http-exception.filter'; +import { Logger } from '@nestjs/common'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { getNatsOptions } from '@credebl/common/nats.config'; +import { CommonConstants } from '@credebl/common/common.constant'; + +const logger = new Logger(); + +async function bootstrap(): Promise { + + const app = await NestFactory.createMicroservice(EcosystemModule, { + transport: Transport.NATS, + options: getNatsOptions(CommonConstants.ECOSYSTEM_SERVICE, process.env.ECOSYSTEM_NKEY_SEED) + }); + + app.useGlobalFilters(new HttpExceptionFilter()); + + await app.listen(); + logger.log('Ecosystem microservice is listening to NATS '); +} +bootstrap(); diff --git a/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts b/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts new file mode 100644 index 000000000..2cba626dd --- /dev/null +++ b/apps/ecosystem/templates/DeleteEcosystemMemberTemplate.ts @@ -0,0 +1,50 @@ +export class DeleteEcosystemMemberTemplate { + + public sendDeleteMemberEmailTemplate( + email: string, + orgName: string, + ecosystemName: string + ): string { + + return ` + + + + + + + + + +
+
+ ${process.env.PLATFORM_NAME} logo +
+ +
+

+ Hello ${email}, +

+

+ We would like to inform you that the organization “${orgName}”, has removed their participation as a member from the "${ecosystemName}" on CREDEBL. + +


+
+
+ For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. +
+

+ © ${process.env.POWERED_BY} +

+
+
+
+ + + `; + + } + + +} \ No newline at end of file diff --git a/apps/ecosystem/templates/EcosystemInviteTemplate.ts b/apps/ecosystem/templates/EcosystemInviteTemplate.ts new file mode 100644 index 000000000..2d1166ea0 --- /dev/null +++ b/apps/ecosystem/templates/EcosystemInviteTemplate.ts @@ -0,0 +1,83 @@ +export class EcosystemInviteTemplate { + + public sendInviteEmailTemplate( + email: string, + ecosystemName: string, + firstName:string, + orgName:string, + isUserExist = false + ): string { + + const validUrl = isUserExist ? `${process.env.FRONT_END_URL}/authentication/sign-in` : `${process.env.FRONT_END_URL}/authentication/sign-up`; + + const message = isUserExist + ? `Please accept the invitation using the following link:` + : `To get started, kindly register on ${process.env.PLATFORM_NAME} platform using this link:`; + + const secondMessage = isUserExist + ? `After successful login into ${process.env.PLATFORM_NAME} and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.` + : `After successful registration, you can log into ${process.env.PLATFORM_NAME} and click on "Accept Ecosystem Invitation" link on your dashboard to start participating in the digital trust ecosystem.`; + + const Button = isUserExist ? `Accept Ecosystem Invitation` : `Register on ${process.env.PLATFORM_NAME}`; + + + return ` + + + + + + + + + +
+
+ ${process.env.PLATFORM_NAME} logo +
+ +
+

+ Hello ${email}, +

+

+ ${firstName} from ${orgName} has invited you to join ${ecosystemName} as an ecosystem member. + +

    +
  • Ecosystem: ${ecosystemName}
  • +
  • Role: Member
  • +
+

+ ${message} +

+ +
+ + + ${Button} + +

Verification Link: ${validUrl}

+
+

${secondMessage}

+
+
+
+ For any assistance or questions while accessing your account, please do not hesitate to contact the support team at ${process.env.PUBLIC_PLATFORM_SUPPORT_EMAIL}. Our team will ensure a seamless onboarding experience for you. + +
+

+ © ${process.env.POWERED_BY} +

+
+
+
+ + + `; + + } + + +} \ No newline at end of file diff --git a/apps/ecosystem/test/app.e2e-spec.ts b/apps/ecosystem/test/app.e2e-spec.ts new file mode 100644 index 000000000..b8483dbc6 --- /dev/null +++ b/apps/ecosystem/test/app.e2e-spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { EcosystemModule } from './../src/ecosystem.module'; + +describe('EcosystemController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [EcosystemModule] + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!')); +}); diff --git a/apps/ecosystem/test/jest-e2e.json b/apps/ecosystem/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/apps/ecosystem/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/apps/ecosystem/tsconfig.app.json b/apps/ecosystem/tsconfig.app.json new file mode 100644 index 000000000..ac6e9e1bb --- /dev/null +++ b/apps/ecosystem/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/ecosystem" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/apps/geo-location/src/geo-location.module.ts b/apps/geo-location/src/geo-location.module.ts index f664a861f..4fc8e2679 100644 --- a/apps/geo-location/src/geo-location.module.ts +++ b/apps/geo-location/src/geo-location.module.ts @@ -8,10 +8,6 @@ import { getNatsOptions } from '@credebl/common/nats.config'; import { PrismaService } from '@credebl/prisma-service'; import { GeoLocationRepository } from './geo-location.repository'; import { CommonConstants } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; @Module({ imports: [ @@ -23,8 +19,6 @@ import { ContextInterceptorModule } from '@credebl/context/contextInterceptorMod } ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, CacheModule.register() ], controllers: [GeoLocationController], diff --git a/apps/geo-location/src/main.ts b/apps/geo-location/src/main.ts index 0e01892c0..8ed718438 100644 --- a/apps/geo-location/src/main.ts +++ b/apps/geo-location/src/main.ts @@ -5,7 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { GeoLocationModule } from './geo-location.module'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -14,7 +13,6 @@ async function bootstrap(): Promise { transport: Transport.NATS, options: getNatsOptions(CommonConstants.GEO_LOCATION_SERVICE, process.env.GEOLOCATION_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/issuance/enum/issuance.enum.ts b/apps/issuance/enum/issuance.enum.ts index 2d25b147f..aa09cabc9 100644 --- a/apps/issuance/enum/issuance.enum.ts +++ b/apps/issuance/enum/issuance.enum.ts @@ -1,29 +1,6 @@ export enum SortFields { - CREATED_DATE_TIME = 'createDateTime', - SCHEMA_ID = 'schemaId', - CONNECTION_ID = 'connectionId', - STATE = 'state' -} - -export enum IssueCredentials { - proposalSent = 'proposal-sent', - proposalReceived = 'proposal-received', - offerSent = 'offer-sent', - offerReceived = 'offer-received', - declined = 'decliend', - requestSent = 'request-sent', - requestReceived = 'request-received', - credentialIssued = 'credential-issued', - credentialReceived = 'credential-received', - done = 'done', - abandoned = 'abandoned' -} - -export enum IssuedCredentialStatus { - offerSent = 'Offered', - done = 'Accepted', - abandoned = 'Declined', - received = 'Pending', - proposalReceived = 'Proposal Received', - credIssued = 'Credential Issued' -} + CREATED_DATE_TIME = 'createDateTime', + SCHEMA_ID = 'schemaId', + CONNECTION_ID = 'connectionId', + STATE = 'state' +} \ No newline at end of file diff --git a/apps/issuance/interfaces/issuance.interfaces.ts b/apps/issuance/interfaces/issuance.interfaces.ts index 6fb847924..8df753f27 100644 --- a/apps/issuance/interfaces/issuance.interfaces.ts +++ b/apps/issuance/interfaces/issuance.interfaces.ts @@ -1,11 +1,10 @@ // eslint-disable-next-line camelcase import { AutoAccept, SchemaType } from '@credebl/enum/enum'; -import { Prisma, organisation } from '@prisma/client'; - -import { IPrettyVc } from '@credebl/common/interfaces/issuance.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; +import { Prisma, organisation } from '@prisma/client'; import { IUserRequestInterface } from 'apps/agent-service/src/interface/agent-service.interface'; import { IssueCredentialType } from 'apps/api-gateway/src/issuance/interfaces'; +import { IPrettyVc } from '@credebl/common/interfaces/issuance.interface'; export interface IAttributes { attributeName: string; @@ -17,8 +16,8 @@ export interface IAttributes { interface ICredentialsAttributes { connectionId: string; attributes: IAttributes[]; - credential?: ICredential; - options?: IOptions; + credential?:ICredential; + options?:IOptions } export interface IIssuance { user?: IUserRequest; @@ -26,28 +25,27 @@ export interface IIssuance { comment?: string; credentialData: ICredentialsAttributes[]; orgId: string; - autoAcceptCredential?: AutoAccept; + autoAcceptCredential?: AutoAccept, protocolVersion?: string; - goalCode?: string; - parentThreadId?: string; - willConfirm?: boolean; - label?: string; - credentialType: string; - isValidateSchema?: string; + goalCode?: string, + parentThreadId?: string, + willConfirm?: boolean, + label?: string, + credentialType: string, } interface IIndy { - attributes: IAttributes[]; - credentialDefinitionId: string; + attributes: IAttributes[], + credentialDefinitionId: string } export interface IIssueData { protocolVersion?: string; connectionId: string; credentialFormats: { - indy: IIndy; - }; - autoAcceptCredential: string; + indy: IIndy + }, + autoAcceptCredential: string, comment?: string; } @@ -92,8 +90,8 @@ export interface IPattern { } export interface ISendOfferNatsPayload { - issueData: IIssueData; - url: string; + issueData: IIssueData, + url: string, apiKey?: string; orgId?: string; } @@ -139,8 +137,8 @@ export interface ICredentialAttributesInterface { value: string; } -export interface ICredential { - '@context': []; +export interface ICredential{ + '@context':[]; type: string[]; prettyVc?: IPrettyVc; issuer?: { @@ -154,15 +152,15 @@ interface ICredentialSubject { [key: string]: string; } -export interface IOptions { - proofType: string; - proofPurpose: string; +export interface IOptions{ + proofType:string; + proofPurpose:string; } export interface CredentialOffer { emailId: string; attributes: IAttributes[]; - credential?: ICredential; - options?: IOptions; + credential?:ICredential; + options?:IOptions } export interface OutOfBandCredentialOfferPayload { credentialDefinitionId?: string; @@ -173,14 +171,13 @@ export interface OutOfBandCredentialOfferPayload { attributes?: IAttributes[]; protocolVersion?: string; isReuseConnection?: boolean; - goalCode?: string; - parentThreadId?: string; - willConfirm?: boolean; - label?: string; - imageUrl?: string; + goalCode?: string, + parentThreadId?: string, + willConfirm?: boolean, + label?: string, + imageUrl?: string, autoAcceptCredential?: string; - credentialType?: IssueCredentialType; - isValidateSchema?: boolean; + credentialType?:IssueCredentialType; } export interface OutOfBandCredentialOffer { @@ -198,34 +195,33 @@ export interface ImportFileDetails { templateId: string; fileKey: string; fileName: string; - type: string; - isValidateSchema?: boolean; + type: string } export interface ICredentialPayload { - schemaLedgerId: string; - credentialDefinitionId: string; - fileData: object; - fileName: string; - credentialType: string; - schemaName?: string; +schemaLedgerId: string, +credentialDefinitionId: string, +fileData: object, +fileName: string, +credentialType: string, +schemaName?: string } export interface PreviewRequest { - pageNumber: number; - pageSize: number; - searchByText: string; - sortField?: string; - sortBy?: string; + pageNumber: number, + pageSize: number, + searchByText: string, + sortField?: string, + sortBy?: string } export interface FileUpload { - name?: string; - upload_type?: string; - status?: string; - orgId?: string; - createDateTime?: Date | null; - lastChangedDateTime?: Date | null; - credentialType?: string; - templateId?: string; + name?: string, + upload_type?: string, + status?: string, + orgId?: string, + createDateTime?: Date | null, + lastChangedDateTime?: Date | null, + credentialType?: string, + templateId?: string } export interface FileUploadData { @@ -249,8 +245,6 @@ export interface IClientDetails { certificate?: string; size?: string; orientation?: string; - height?: string; - width?: string; } export interface IIssuedCredentialsSearchInterface { issuedCredentialsSearchCriteria: IIssuedCredentialsSearchCriteria; @@ -287,22 +281,21 @@ export interface SendEmailCredentialOffer { iterator: CredentialOffer; emailId: string; index: number; - credentialType: IssueCredentialType; + credentialType: IssueCredentialType; protocolVersion: string; isReuseConnection?: boolean; - attributes: IAttributes[]; - credentialDefinitionId: string; + attributes: IAttributes[]; + credentialDefinitionId: string; outOfBandCredential: OutOfBandCredentialOfferPayload; comment: string; - organisation: organisation; + organisation: organisation; errors; url: string; - orgId: string; + orgId: string; organizationDetails: organisation; - platformName?: string; + platformName?: string, organizationLogoUrl?: string; prettyVc?: IPrettyVc; - isValidateSchema?: boolean; } export interface TemplateDetailsInterface { @@ -324,16 +317,15 @@ export interface IJobDetails { schemaLedgerId: string; credentialDefinitionId?: string; status?: boolean; - credential_data: CredentialData; + credential_data: CredentialData orgId: string; credentialType: string; } -export interface IQueuePayload { +export interface IQueuePayload{ id: string; jobId: string; cacheId?: string; - isValidateSchema?: boolean; clientId: string; referenceId: string; fileUploadId: string; @@ -351,25 +343,23 @@ export interface IQueuePayload { certificate?: string; size?: string; orientation?: string; - height?: string; - width?: string; isReuseConnection?: boolean; } interface FileDetails { schemaLedgerId: string; credentialDefinitionId: string; - fileData: object; + fileData:object fileName: string; credentialType: string; schemaName: string; } export interface IBulkPayloadObject { - parsedData?: unknown[]; - parsedFileDetails?: FileDetails; - userId: string; - fileUploadId: string; -} + parsedData?: unknown[], + parsedFileDetails?: FileDetails, + userId: string, + fileUploadId: string + }; export interface ISchemaAttributes { attributeName: string; schemaDataType: string; @@ -389,17 +379,10 @@ export interface BulkPayloadDetails { clientId: string; orgId: string; requestId?: string; - isValidateSchema?: boolean; isRetry: boolean; organizationLogoUrl?: string; platformName?: string; certificate?: string; size?: string; orientation?: string; - height?: string; - width?: string; -} - -export interface ISchemaId { - schemaLedgerId: string; } diff --git a/apps/issuance/libs/helpers/attributes.extractor.ts b/apps/issuance/libs/helpers/attributes.extractor.ts deleted file mode 100644 index 5c62aaa93..000000000 --- a/apps/issuance/libs/helpers/attributes.extractor.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { CommonConstants } from '@credebl/common/common.constant'; -import { TemplateIdentifier } from '@credebl/enum/enum'; - -// Function for extracting attributes from nested structure -export function extractAttributeNames( - attributeObj, - parentKey: string = '', - result: Set = new Set(), - inNestedArray: boolean = false -): string[] { - if (Array.isArray(attributeObj)) { - attributeObj.forEach((item) => { - extractAttributeNames(item, parentKey, result, inNestedArray); - }); - } else if ('object' === typeof attributeObj && null !== attributeObj) { - let newParentKey = parentKey; - - if (attributeObj.hasOwnProperty('attributeName')) { - newParentKey = parentKey - ? `${parentKey}${CommonConstants.NESTED_ATTRIBUTE_SEPARATOR}${attributeObj.attributeName}` - : attributeObj.attributeName; - } - - if (attributeObj.hasOwnProperty('items') && Array.isArray(attributeObj.items)) { - // Always use index 0 for items in an array - attributeObj.items.forEach((item) => { - extractAttributeNames(item, `${newParentKey}${CommonConstants.NESTED_ATTRIBUTE_SEPARATOR}0`, result, true); - }); - } else if (attributeObj.hasOwnProperty('properties')) { - Object.entries(attributeObj.properties).forEach(([key, value]) => { - const propertyKey = `${newParentKey}${CommonConstants.NESTED_ATTRIBUTE_SEPARATOR}${key}`; - extractAttributeNames(value, propertyKey, result, inNestedArray); - }); - } else { - Object.entries(attributeObj).forEach(([key, value]) => { - if (!['attributeName', 'items', 'properties'].includes(key)) { - extractAttributeNames(value, newParentKey, result, inNestedArray); - } - }); - } - } else { - result.add(parentKey); - } - - return Array.from(result); -} - -// Handles both explicitly indexed arrays and implicit arrays -function mergeArrayObjects(obj): void { - if (!obj || 'object' !== typeof obj) { - return; - } - - // First pass: Convert objects with numeric keys to arrays - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const value = obj[key]; - - // Skip non-objects - if (!value || 'object' !== typeof value) { - continue; - } - - // Process arrays - if (Array.isArray(value)) { - value.forEach((item) => { - if (item && 'object' === typeof item) { - mergeArrayObjects(item); - } - }); - } else { - // Process objects - // Check if this object has numeric keys - const keys = Object.keys(value); - const numericKeys = keys.filter((k) => /^\d+$/.test(k)); - - if (0 < numericKeys.length) { - // Has numeric keys - convert to array - const tempArray = []; - - // First, add all numeric keys to the array - numericKeys - .sort((a, b) => parseInt(a) - parseInt(b)) - .forEach((k) => { - const index = parseInt(k); - tempArray[index] = value[k]; - - // Process recursively - if (value[k] && 'object' === typeof value[k]) { - mergeArrayObjects(value[k]); - } - }); - - // Then add all non-numeric keys to every array element - const nonNumericKeys = keys.filter((k) => !/^\d+$/.test(k)); - if (0 < nonNumericKeys.length) { - tempArray.forEach((item, index) => { - if (!item || 'object' !== typeof item) { - tempArray[index] = {}; - } - - nonNumericKeys.forEach((k) => { - tempArray[index][k] = value[k]; - }); - }); - } - - // Replace the object with our array - obj[key] = tempArray; - } else { - // No numeric keys - process recursively - mergeArrayObjects(value); - } - } - } - } - - // Second pass: Look for arrays with objects that have common prefixes with numeric suffixes - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - if (Array.isArray(obj[key])) { - // Look for patterns like "field1", "field2" in each array element - obj[key].forEach((item) => { - if (item && 'object' === typeof item) { - const keys = Object.keys(item); - const prefixMap = new Map(); - - // Group keys by prefix - keys.forEach((k) => { - const match = k.match(/^([^0-9]+)(\d{1,10})$/); - if (match) { - // eslint-disable-next-line prefer-destructuring - const prefix = match[1]; - const index = parseInt(match[2]); - if (!prefixMap.has(prefix)) { - prefixMap.set(prefix, []); - } - prefixMap.get(prefix).push({ key: k, index }); - } - }); - - // Convert grouped prefixes to arrays - for (const [prefix, matches] of prefixMap.entries()) { - if (0 < matches.length) { - const tempArray = []; - - // Sort by index and populate array - matches - .sort((a, b) => a.index - b.index) - .forEach((match) => { - tempArray[match.index] = item[match.key]; - delete item[match.key]; - }); - - // Set the array on the item - item[prefix] = tempArray; - } - } - - // Process recursively - mergeArrayObjects(item); - } - }); - } - } - } -} - -// Helper function to process remaining parts of a key path -function processRemainingParts(obj, parts: string[], value): void { - let current = obj; - - for (let i = 0; i < parts.length; i++) { - const part = parts[i]; - - // If this is the last part, set the value - if (i === parts.length - 1) { - current[part] = value; - return; - } - - // Check if next part is a number (array index) - const isNextPartNumeric = i < parts.length - 1 && !isNaN(Number(parts[i + 1])); - - if (isNextPartNumeric) { - // This is an array index, create array if needed - if (!current[part]) { - current[part] = []; - } - - const index = parseInt(parts[i + 1]); - while (current[part].length <= index) { - current[part].push({}); - } - - // Update the local variable instead of the parameter - current = current[part][index]; - i++; // Skip the index part - } else { - // This is a regular object property - if (!current[part]) { - current[part] = {}; - } - - current = current[part]; - } - } -} - -// Function to convert a flattened CSV row into a nested object -export function unflattenCsvRow(row: object): object { - const result: object = {}; - const groupedKeys: Record = {}; - - // First pass: handle simple keys and identify complex keys - for (const key in row) { - if (Object.prototype.hasOwnProperty.call(row, key)) { - // Skip empty values - if ('' === row[key]) { - continue; - } - - // Handle email identifier specially - if (TemplateIdentifier.EMAIL_COLUMN === key) { - result[key] = row[key]; - continue; - } - - const keyParts = key.split(CommonConstants.NESTED_ATTRIBUTE_SEPARATOR); - - // Handle array notation: key~index~otherParts - if (1 < keyParts.length && !isNaN(Number(keyParts[1]))) { - // eslint-disable-next-line prefer-destructuring - const arrayName = keyParts[0]; - // eslint-disable-next-line prefer-destructuring - const arrayIndex = keyParts[1]; - const groupKey = `${arrayName}${CommonConstants.NESTED_ATTRIBUTE_SEPARATOR}${arrayIndex}`; - - if (!groupedKeys[groupKey]) { - groupedKeys[groupKey] = []; - } - groupedKeys[groupKey].push(key); - } else { - // Handle implicit array notation or simple nested keys - - // Check if this key has any numeric part that might indicate an array - const hasArrayPart = keyParts.some((part, index) => 0 < index && !isNaN(Number(part)) && '' !== part); - - if (hasArrayPart) { - // Handle as potential array, but we'll process it in the second pass - if (!groupedKeys[keyParts[0]]) { - groupedKeys[keyParts[0]] = []; - } - groupedKeys[keyParts[0]].push(key); - } else { - // Handle as simple nested key (no arrays) - let currentLevel = result; - for (let i = 0; i < keyParts.length; i++) { - const part = keyParts[i]; - if (i === keyParts.length - 1) { - currentLevel[part] = row[key]; - } else { - if (!currentLevel[part]) { - currentLevel[part] = {}; - } - currentLevel = currentLevel[part]; - } - } - } - } - } - } - - // Second pass: process explicitly indexed arrays - for (const grpKey in groupedKeys) { - if (Object.prototype.hasOwnProperty.call(groupedKeys, grpKey)) { - const keys = groupedKeys[grpKey]; - - // For explicit indexed arrays (format: arrayName~index~...) - if (grpKey.includes(CommonConstants.NESTED_ATTRIBUTE_SEPARATOR)) { - const [arrayName, arrayIndex] = grpKey.split(CommonConstants.NESTED_ATTRIBUTE_SEPARATOR); - - if (!result[arrayName]) { - result[arrayName] = []; - } - - const index = parseInt(arrayIndex); - while (result[arrayName].length <= index) { - result[arrayName].push({}); - } - - for (const key of keys) { - const keyParts = key.split(CommonConstants.NESTED_ATTRIBUTE_SEPARATOR); - const remainingParts = keyParts.slice(2); - const currentLevel = result[arrayName][index]; - - processRemainingParts(currentLevel, remainingParts, row[key]); - } - } else { - // For implicit arrays (format: arrayName~field~0~...) - const arrayName = grpKey; - - if (!result[arrayName]) { - result[arrayName] = {}; - } - - for (const key of keys) { - const keyParts = key.split(CommonConstants.NESTED_ATTRIBUTE_SEPARATOR); - const remainingParts = keyParts.slice(1); - const currentLevel = result[arrayName]; - - processRemainingParts(currentLevel, remainingParts, row[key]); - } - } - } - } - - mergeArrayObjects(result); - return result; -} diff --git a/apps/issuance/libs/helpers/attributes.validator.ts b/apps/issuance/libs/helpers/attributes.validator.ts index 58a961166..78df951e9 100644 --- a/apps/issuance/libs/helpers/attributes.validator.ts +++ b/apps/issuance/libs/helpers/attributes.validator.ts @@ -1,14 +1,11 @@ -/* eslint-disable no-console */ import { W3CSchemaDataType } from '@credebl/enum/enum'; -import { BadRequestException, Logger } from '@nestjs/common'; +import { BadRequestException } from '@nestjs/common'; import { IIssuanceAttributes, ISchemaAttributes } from 'apps/issuance/interfaces/issuance.interfaces'; export function validateW3CSchemaAttributes( filteredIssuanceAttributes: IIssuanceAttributes, - schemaUrlAttributes: ISchemaAttributes[], - logger: Logger + schemaUrlAttributes: ISchemaAttributes[] ): void { - logger.debug('Validating w3c schema attributes'); const mismatchedAttributes: string[] = []; const missingAttributes: string[] = []; const extraAttributes: string[] = []; @@ -33,10 +30,9 @@ export function validateW3CSchemaAttributes( const actualType = typeof attributeValue; // Check if the schemaDataType is 'datetime-local' and treat it as a string - if ( - (W3CSchemaDataType.DATE_TIME === schemaDataType && W3CSchemaDataType.STRING !== actualType) || - (W3CSchemaDataType.DATE_TIME !== schemaDataType && actualType !== schemaDataType) - ) { + if ((W3CSchemaDataType.DATE_TIME === schemaDataType && W3CSchemaDataType.STRING !== actualType) || + (W3CSchemaDataType.DATE_TIME !== schemaDataType && actualType !== schemaDataType)) { + mismatchedAttributes.push( `Attribute ${attributeName} has type ${actualType} but expected type ${schemaDataType}` ); @@ -61,6 +57,4 @@ export function validateW3CSchemaAttributes( if (0 < extraAttributes.length) { throw new BadRequestException(`Validation failed: ${extraAttributes.join(', ')}`); } - // TODO: Maybe we can have a try catch here, so that instead of directly throwing the erros - logger.debug('W3c schema attributes validated successfully'); } diff --git a/apps/issuance/src/issuance.controller.ts b/apps/issuance/src/issuance.controller.ts index b1bbf6bcc..dc5401d3c 100644 --- a/apps/issuance/src/issuance.controller.ts +++ b/apps/issuance/src/issuance.controller.ts @@ -1,33 +1,18 @@ -import { - IClientDetails, - IIssuance, - IIssueCredentials, - IIssueCredentialsDefinitions, - ImportFileDetails, - IssueCredentialWebhookPayload, - OutOfBandCredentialOffer, - PreviewRequest, - TemplateDetailsInterface -} from '../interfaces/issuance.interfaces'; -import { - ICredentialOfferResponse, - IDeletedIssuanceRecords, - IIssuedCredential -} from '@credebl/common/interfaces/issuance.interface'; - -import { Controller, Logger } from '@nestjs/common'; -import { IssuanceService } from './issuance.service'; +import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; +import { IClientDetails, IIssuance, IIssueCredentials, IIssueCredentialsDefinitions, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOffer, PreviewRequest, TemplateDetailsInterface } from '../interfaces/issuance.interfaces'; +import { IssuanceService } from './issuance.service'; +import { ICredentialOfferResponse, IDeletedIssuanceRecords, IIssuedCredential } from '@credebl/common/interfaces/issuance.interface'; import { OOBIssueCredentialDto } from 'apps/api-gateway/src/issuance/dtos/issuance.dto'; -import { user } from '@prisma/client'; +// eslint-disable-next-line camelcase +import { org_agents, user } from '@prisma/client'; @Controller() export class IssuanceController { - private readonly logger = new Logger('IssueCredentialController'); - constructor(private readonly issuanceService: IssuanceService) {} + constructor(private readonly issuanceService: IssuanceService) { } @MessagePattern({ cmd: 'get-issuance-records' }) - async getIssuanceRecordsByOrgId(payload: { orgId: string; userId: string }): Promise { + async getIssuanceRecordsByOrgId(payload: { orgId: string, userId: string }): Promise { const { orgId } = payload; return this.issuanceService.getIssuanceRecords(orgId); } @@ -38,7 +23,7 @@ export class IssuanceController { } @MessagePattern({ cmd: 'send-credential-create-offer-oob' }) - async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{ response: object }> { + async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{response: object;}> { return this.issuanceService.sendCredentialOutOfBand(payload); } @@ -54,85 +39,71 @@ export class IssuanceController { return this.issuanceService.getIssueCredentialsbyCredentialRecordId(user, credentialRecordId, orgId); } + @MessagePattern({ cmd: 'webhook-get-issue-credential' }) - async getIssueCredentialWebhook(payload: IssueCredentialWebhookPayload): Promise { + // eslint-disable-next-line camelcase + async getIssueCredentialWebhook(payload: IssueCredentialWebhookPayload): Promise { return this.issuanceService.getIssueCredentialWebhook(payload); } @MessagePattern({ cmd: 'out-of-band-credential-offer' }) async outOfBandCredentialOffer(payload: OutOfBandCredentialOffer): Promise { const { outOfBandCredentialDto } = payload; - this.logger.debug('Request reached issuance microservice controller, issuing oob credential'); return this.issuanceService.outOfBandCredentialOffer(outOfBandCredentialDto); } @MessagePattern({ cmd: 'download-csv-template-for-bulk-operation' }) async downloadBulkIssuanceCSVTemplate(payload: { - orgId: string; - templateDetails: TemplateDetailsInterface; + orgId: string, templateDetails: TemplateDetailsInterface }): Promise { - const { orgId, templateDetails } = payload; - return this.issuanceService.downloadBulkIssuanceCSVTemplate(orgId, templateDetails); + const {templateDetails} = payload; + return this.issuanceService.downloadBulkIssuanceCSVTemplate(templateDetails); } @MessagePattern({ cmd: 'upload-csv-template' }) - async uploadCSVTemplate(payload: { importFileDetails: ImportFileDetails; orgId: string }): Promise { - return this.issuanceService.uploadCSVTemplate(payload.importFileDetails, payload.orgId); + async uploadCSVTemplate(payload: { + importFileDetails: ImportFileDetails + }): Promise { + return this.issuanceService.uploadCSVTemplate(payload.importFileDetails); } @MessagePattern({ cmd: 'preview-csv-details' }) - async previewCSVDetails(payload: { requestId: string; previewFileDetails: PreviewRequest }): Promise { - return this.issuanceService.previewFileDataForIssuance(payload.requestId, payload.previewFileDetails); + async previewCSVDetails(payload: { requestId: string, previewFileDetails: PreviewRequest }): Promise { + return this.issuanceService.previewFileDataForIssuance( + payload.requestId, + payload.previewFileDetails + ); } @MessagePattern({ cmd: 'issued-file-details' }) - async issuedFiles(payload: { orgId: string; fileParameter: PreviewRequest }): Promise { - return this.issuanceService.issuedFileDetails(payload.orgId, payload.fileParameter); + async issuedFiles(payload: { orgId: string, fileParameter: PreviewRequest }): Promise { + return this.issuanceService.issuedFileDetails( + payload.orgId, + payload.fileParameter + ); } @MessagePattern({ cmd: 'issued-file-data' }) - async getFileDetailsByFileId(payload: { fileId: string; fileParameter: PreviewRequest }): Promise { - return this.issuanceService.getFileDetailsByFileId(payload.fileId, payload.fileParameter); + async getFileDetailsByFileId(payload: { fileId: string, fileParameter: PreviewRequest }): Promise { + return this.issuanceService.getFileDetailsByFileId( + payload.fileId, + payload.fileParameter + ); } + @MessagePattern({ cmd: 'issue-bulk-credentials' }) - async issueBulkCredentials(payload: { - requestId: string; - orgId: string; - clientDetails: IClientDetails; - reqPayload: ImportFileDetails; - isValidateSchema: boolean; - }): Promise { - return this.issuanceService.issueBulkCredential( - payload.requestId, - payload.orgId, - payload.clientDetails, - payload.reqPayload, - payload.isValidateSchema - ); + async issueBulkCredentials(payload: { requestId: string, orgId: string, clientDetails: IClientDetails, reqPayload: ImportFileDetails }): Promise { + return this.issuanceService.issueBulkCredential(payload.requestId, payload.orgId, payload.clientDetails, payload.reqPayload); } @MessagePattern({ cmd: 'retry-bulk-credentials' }) - async retryeBulkCredentials(payload: { - fileId: string; - orgId: string; - clientDetails: IClientDetails; - isValidateSchema?: boolean; - }): Promise { - return this.issuanceService.retryBulkCredential( - payload.fileId, - payload.orgId, - payload.clientDetails, - payload.isValidateSchema - ); + async retryeBulkCredentials(payload: { fileId: string, orgId: string, clientDetails: IClientDetails }): Promise { + return this.issuanceService.retryBulkCredential(payload.fileId, payload.orgId, payload.clientDetails); } @MessagePattern({ cmd: 'delete-issuance-records' }) - async deleteIssuanceRecords(payload: { orgId: string; userDetails: user }): Promise { + async deleteIssuanceRecords(payload: {orgId: string, userDetails: user}): Promise { const { orgId, userDetails } = payload; return this.issuanceService.deleteIssuanceRecords(orgId, userDetails); } - @MessagePattern({ cmd: 'issued-file-data-and-file-details' }) - async getFileDetailsAndFileDataByFileId(payload: { fileId: string; orgId: string }): Promise { - return this.issuanceService.getFileDetailsAndFileDataByFileId(payload.fileId, payload.orgId); - } } diff --git a/apps/issuance/src/issuance.module.ts b/apps/issuance/src/issuance.module.ts index 76e5cf912..fd5fc0152 100644 --- a/apps/issuance/src/issuance.module.ts +++ b/apps/issuance/src/issuance.module.ts @@ -11,15 +11,11 @@ import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; import { BullModule } from '@nestjs/bull'; import { CacheModule } from '@nestjs/cache-manager'; +import * as redisStore from 'cache-manager-redis-store'; import { BulkIssuanceProcessor } from './issuance.processor'; import { AwsService } from '@credebl/aws'; import { UserActivityRepository } from 'libs/user-activity/repositories'; -import { CommonConstants, MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { NATSClient } from '@credebl/common/NATSClient'; +import { CommonConstants } from '@credebl/common/common.constant'; @Module({ imports: [ @@ -32,11 +28,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; } ]), CommonModule, - GlobalConfigModule, - LoggerModule, - PlatformConfig, - ContextInterceptorModule, - CacheModule.register(), + CacheModule.register({ store: redisStore, host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }), BullModule.forRoot({ redis: { host: process.env.REDIS_HOST, @@ -48,22 +40,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; }) ], controllers: [IssuanceController], - providers: [ - IssuanceService, - IssuanceRepository, - UserActivityRepository, - PrismaService, - Logger, - OutOfBandIssuance, - EmailDto, - BulkIssuanceProcessor, - AwsService, - NATSClient, - { - provide: MICRO_SERVICE_NAME, - useValue: 'IssuanceService' - } - ], - exports: [CacheModule] + providers: [IssuanceService, IssuanceRepository, UserActivityRepository, PrismaService, Logger, OutOfBandIssuance, EmailDto, BulkIssuanceProcessor, AwsService] }) -export class IssuanceModule {} +export class IssuanceModule { } diff --git a/apps/issuance/src/issuance.processor.ts b/apps/issuance/src/issuance.processor.ts index 23365f909..be45d2a36 100644 --- a/apps/issuance/src/issuance.processor.ts +++ b/apps/issuance/src/issuance.processor.ts @@ -11,12 +11,16 @@ export class BulkIssuanceProcessor { @OnQueueActive() onActive(job: Job): void { - this.logger.log(`Emitting job status${job.id} of type ${job.name} ...`); + this.logger.log( + `Emitting job status${job.id} of type ${job.name} with data ${JSON.stringify(job.data)}...` + ); } @Process() - async issueCredential(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name} ...`); + async issueCredential(job: Job):Promise { + this.logger.log( + `Processing job ${job.id} of type ${job.name} with data ${JSON.stringify(job.data)}...` + ); this.issuanceService.processIssuanceData(job.data); } diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 8fa95635a..aee434fd4 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -1,20 +1,9 @@ /* eslint-disable camelcase */ import { ConflictException, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; -import { - FileUpload, - FileUploadData, - IDeletedFileUploadRecords, - IssueCredentialWebhookPayload, - OrgAgent, - PreviewRequest, - SchemaDetails -} from '../interfaces/issuance.interfaces'; -import { IssueCredentials, IssuedCredentialStatus } from '../enum/issuance.enum'; +import { PrismaService } from '@credebl/prisma-service'; // eslint-disable-next-line camelcase import { - Prisma, agent_invitations, - credentials, file_data, file_upload, org_agents, @@ -22,15 +11,21 @@ import { platform_config, schema } from '@prisma/client'; -import { PrismaTables, SortValue } from '@credebl/enum/enum'; - +import { ResponseMessages } from '@credebl/common/response-messages'; +import { + FileUpload, + FileUploadData, + IDeletedFileUploadRecords, + IssueCredentialWebhookPayload, + OrgAgent, + PreviewRequest, + SchemaDetails +} from '../interfaces/issuance.interfaces'; import { FileUploadStatus } from 'apps/api-gateway/src/enum'; -import { IDeletedIssuanceRecords } from '@credebl/common/interfaces/issuance.interface'; -import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/interfaces'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { PrismaService } from '@credebl/prisma-service'; -import { ResponseMessages } from '@credebl/common/response-messages'; - +import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/interfaces'; +import { PrismaTables, SortValue } from '@credebl/enum/enum'; +import { IDeletedIssuanceRecords } from '@credebl/common/interfaces/issuance.interface'; @Injectable() export class IssuanceRepository { constructor( @@ -93,11 +88,34 @@ export class IssuanceRepository { } } - async getInvitationDidByOrgId(orgId: string): Promise { + async getOrganizationByOrgId(orgId: string): Promise { try { - return this.prisma.agent_invitations.findMany({ + return this.prisma.org_agents.findFirst({ where: { orgId + } + }); + } catch (error) { + this.logger.error(`Error in getOrganization in issuance repository: ${error.message} `); + throw error; + } + } + + + async getInvitationDidByOrgId(orgId: string): Promise { + try { + return this.prisma.agent_invitations.findFirst({ + where: { + AND: [ + { + orgId + }, + { + invitationDid: { + not: null + } + } + ] }, orderBy: { createDateTime: 'asc' @@ -112,8 +130,7 @@ export class IssuanceRepository { async getAllIssuedCredentials( user: IUserRequest, orgId: string, - issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams, - schemaIds?: string[] + issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams ): Promise<{ issuedCredentialsCount: number; issuedCredentialsList: { @@ -123,72 +140,17 @@ export class IssuanceRepository { schemaId: string; state: string; orgId: string; - connections: { - theirLabel: string; - }; }[]; }> { try { - const schemas = await this.prisma.schema.findMany({ + const issuedCredentialsList = await this.prisma.credentials.findMany({ where: { - name: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } + orgId, + OR: [ + { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } + ] }, - select: { schemaLedgerId: true } - }); - - const schemaIdsMatched = schemas.map((s) => s.schemaLedgerId); - let stateInfo = null; - switch (issuedCredentialsSearchCriteria.search.toLowerCase()) { - case IssuedCredentialStatus.offerSent.toLowerCase(): - stateInfo = IssueCredentials.offerSent; - break; - - case IssuedCredentialStatus.done.toLowerCase(): - stateInfo = IssueCredentials.done; - break; - - case IssuedCredentialStatus.abandoned.toLowerCase(): - stateInfo = IssueCredentials.abandoned; - break; - - case IssuedCredentialStatus.received.toLowerCase(): - stateInfo = IssueCredentials.requestReceived; - break; - - case IssuedCredentialStatus.proposalReceived.toLowerCase(): - stateInfo = IssueCredentials.proposalReceived; - break; - - case IssuedCredentialStatus.credIssued.toLowerCase(): - stateInfo = IssueCredentials.offerSent; - break; - - default: - break; - } - - const issuanceWhereClause: Prisma.credentialsWhereInput = { - orgId, - ...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}), - ...(!schemaIds?.length && issuedCredentialsSearchCriteria.search - ? { - OR: [ - { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, - { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, - { schemaId: { in: schemaIdsMatched } }, - { - connections: { - theirLabel: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } - } - }, - { state: { contains: stateInfo ?? issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } - ] - } - : {}) - }; - - const issuedCredentialsList = await this.prisma.credentials.findMany({ - where: issuanceWhereClause, select: { credentialExchangeId: true, createDateTime: true, @@ -196,12 +158,7 @@ export class IssuanceRepository { orgId: true, state: true, schemaId: true, - connectionId: true, - connections: { - select: { - theirLabel: true - } - } + connectionId: true }, orderBy: { [issuedCredentialsSearchCriteria?.sortField]: @@ -210,8 +167,15 @@ export class IssuanceRepository { take: Number(issuedCredentialsSearchCriteria.pageSize), skip: (issuedCredentialsSearchCriteria.pageNumber - 1) * issuedCredentialsSearchCriteria.pageSize }); + const issuedCredentialsCount = await this.prisma.credentials.count({ - where: issuanceWhereClause + where: { + orgId, + OR: [ + { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } + ] + } }); return { issuedCredentialsCount, issuedCredentialsList }; @@ -227,30 +191,33 @@ export class IssuanceRepository { * @returns Get saved credential details */ // eslint-disable-next-line camelcase - async saveIssuedCredentialDetails(payload: IssueCredentialWebhookPayload): Promise { + async saveIssuedCredentialDetails(payload: IssueCredentialWebhookPayload): Promise { try { let organisationId: string; + let agentOrg: org_agents; const { issueCredentialDto, id } = payload; - if ('default' !== issueCredentialDto?.contextCorrelationId) { - const getOrganizationId = await this.getOrganizationByTenantId(issueCredentialDto?.contextCorrelationId); - organisationId = getOrganizationId?.orgId; + agentOrg = await this.getOrganizationByTenantId(issueCredentialDto?.contextCorrelationId); + if (agentOrg?.orgId) { + organisationId = agentOrg?.orgId; + } else { + agentOrg = await this.getOrganizationByOrgId(id); + organisationId = id; + } } else { + agentOrg = await this.getOrganizationByOrgId(id); organisationId = id; } let schemaId = ''; - if ( - issueCredentialDto?.metadata?.['_anoncreds/credential']?.schemaId || - issueCredentialDto?.['credentialData']?.offer?.jsonld?.credential?.['@context'][1] || - (issueCredentialDto?.state && - issueCredentialDto?.['credentialData']?.proposal?.jsonld?.credential?.['@context'][1]) - ) { - schemaId = - issueCredentialDto?.metadata?.['_anoncreds/credential']?.schemaId || - issueCredentialDto?.['credentialData']?.offer?.jsonld?.credential?.['@context'][1] || - issueCredentialDto?.['credentialData']?.proposal?.jsonld?.credential?.['@context'][1]; + if ( + (issueCredentialDto?.metadata?.['_anoncreds/credential']?.schemaId || + issueCredentialDto?.['credentialData']?.offer?.jsonld?.credential?.['@context'][1]) || + (issueCredentialDto?.state && + issueCredentialDto?.['credentialData']?.proposal?.jsonld?.credential?.['@context'][1]) + ) { + schemaId = issueCredentialDto?.metadata?.['_anoncreds/credential']?.schemaId || issueCredentialDto?.['credentialData']?.offer?.jsonld?.credential?.['@context'][1] || issueCredentialDto?.['credentialData']?.proposal?.jsonld?.credential?.['@context'][1]; } let credDefId = ''; @@ -258,7 +225,7 @@ export class IssuanceRepository { credDefId = issueCredentialDto?.metadata?.['_anoncreds/credential']?.credentialDefinitionId; } - const credentialDetails = await this.prisma.credentials.upsert({ + await this.prisma.credentials.upsert({ where: { threadId: issueCredentialDto?.threadId }, @@ -268,8 +235,8 @@ export class IssuanceRepository { threadId: issueCredentialDto?.threadId, connectionId: issueCredentialDto?.connectionId, state: issueCredentialDto?.state, - credDefId, - ...(schemaId ? { schemaId } : {}) + schemaId, + credDefId }, create: { createDateTime: issueCredentialDto?.createDateTime, @@ -285,7 +252,7 @@ export class IssuanceRepository { } }); - return credentialDetails; + return agentOrg; } catch (error) { this.logger.error(`Error in get saveIssuedCredentialDetails: ${error.message} `); throw error; @@ -322,8 +289,7 @@ export class IssuanceRepository { async getSchemaDetails(schemaId: string): Promise { try { - //Todo: Enhance this query by using FindFirstOrThrow - const schemaDetails = await this.prisma.schema.findFirst({ + const schemaDetails = await this.prisma.schema.findFirstOrThrow({ where: { schemaLedgerId: schemaId } @@ -350,6 +316,7 @@ export class IssuanceRepository { const schemaDetails = await this.getSchemaDetailsBySchemaIdentifier(credentialDefinitionDetails.schemaLedgerId); + if (!schemaDetails) { throw new NotFoundException(`Schema not found for credential definition ID: ${credentialDefinitionId}`); } @@ -365,12 +332,12 @@ export class IssuanceRepository { return credentialDefRes; } catch (error) { this.logger.error(`Error in getCredentialDefinitionDetails: ${error.message}`); - throw error; + throw new InternalServerErrorException(error.message); } } async getSchemaDetailsBySchemaIdentifier(schemaIdentifier: string): Promise { - const schemaDetails = await this.prisma.schema.findFirst({ + const schemaDetails = await this.prisma.schema.findFirstOrThrow({ where: { schemaLedgerId: schemaIdentifier } @@ -692,6 +659,7 @@ export class IssuanceRepository { async deleteFileUploadData(fileUploadIds: string[], orgId: string): Promise { try { return await this.prisma.$transaction(async (prisma) => { + const deleteFileDetails = await prisma.file_data.deleteMany({ where: { fileUploadId: { @@ -707,6 +675,7 @@ export class IssuanceRepository { }); return { deleteFileDetails, deleteFileUploadDetails }; + }); } catch (error) { this.logger.error(`[Error in deleting file data] - error: ${JSON.stringify(error)}`); @@ -739,6 +708,7 @@ export class IssuanceRepository { } return await this.prisma.$transaction(async (prisma) => { + const recordsToDelete = await this.prisma.credentials.findMany({ where: { orgId }, select: { @@ -755,42 +725,11 @@ export class IssuanceRepository { where: { orgId } }); - return { deleteResult, recordsToDelete }; + return { deleteResult, recordsToDelete}; }); } catch (error) { this.logger.error(`Error in deleting issuance records: ${error.message}`); throw error; } } - async getFileDetailsAndFileDataByFileId(fileId: string, orgId: string): Promise { - try { - const fileDetails = await this.prisma.file_upload.findUnique({ - where: { - id: fileId, - orgId - }, - include: { - file_data: true - } - }); - return fileDetails; - } catch (error) { - this.logger.error(`[getFileDetailsAndFileDataByFileId] - error: ${JSON.stringify(error)}`); - throw error; - } - } - - async updateSchemaIdByThreadId(threadId: string, schemaId: string): Promise { - try { - await this.prisma.credentials.update({ - where: { threadId }, - data: { - schemaId - } - }); - } catch (error) { - this.logger.error(`[updateSchemaIdByThreadId] - error: ${JSON.stringify(error)}`); - throw error; - } - } } diff --git a/apps/issuance/src/issuance.service.ts b/apps/issuance/src/issuance.service.ts index 33b630113..728265dc0 100644 --- a/apps/issuance/src/issuance.service.ts +++ b/apps/issuance/src/issuance.service.ts @@ -2,56 +2,15 @@ /* eslint-disable no-useless-catch */ /* eslint-disable camelcase */ import { CommonService } from '@credebl/common'; -import { - BadRequestException, - ConflictException, - HttpException, - HttpStatus, - Inject, - Injectable, - InternalServerErrorException, - Logger, - NotFoundException -} from '@nestjs/common'; +import { BadRequestException, ConflictException, HttpException, HttpStatus, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { IssuanceRepository } from './issuance.repository'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { CommonConstants } from '@credebl/common/common.constant'; import { ResponseMessages } from '@credebl/common/response-messages'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs'; -import { - BulkPayloadDetails, - CredentialOffer, - FileUpload, - FileUploadData, - IAttributes, - IBulkPayloadObject, - IClientDetails, - ICreateOfferResponse, - ICredentialPayload, - IIssuance, - IIssueData, - IPattern, - IQueuePayload, - ISchemaAttributes, - ISchemaId, - ISendOfferNatsPayload, - ImportFileDetails, - IssueCredentialWebhookPayload, - OutOfBandCredentialOfferPayload, - PreviewRequest, - SchemaDetails, - SendEmailCredentialOffer, - TemplateDetailsInterface -} from '../interfaces/issuance.interfaces'; -import { - AutoAccept, - IssuanceProcessState, - PromiseResult, - SchemaType, - TemplateIdentifier, - W3CSchemaDataType -} from '@credebl/enum/enum'; +import { BulkPayloadDetails, CredentialOffer, FileUpload, FileUploadData, IAttributes, IBulkPayloadObject, IClientDetails, ICreateOfferResponse, ICredentialPayload, IIssuance, IIssueData, IPattern, IQueuePayload, ISchemaAttributes, ISendOfferNatsPayload, ImportFileDetails, IssueCredentialWebhookPayload, OutOfBandCredentialOfferPayload, PreviewRequest, SchemaDetails, SendEmailCredentialOffer, TemplateDetailsInterface } from '../interfaces/issuance.interfaces'; +import { AutoAccept, IssuanceProcessState, OrgAgentType, PromiseResult, SchemaType, TemplateIdentifier, W3CSchemaDataType} from '@credebl/enum/enum'; import * as QRCode from 'qrcode'; import { OutOfBandIssuance } from '../templates/out-of-band-issuance.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; @@ -62,37 +21,22 @@ import { parse as paParse } from 'papaparse'; import { v4 as uuidv4 } from 'uuid'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; -import { convertUrlToDeepLinkUrl, getAgentUrl, paginator } from '@credebl/common/common.utils'; +import { convertUrlToDeepLinkUrl, paginator } from '@credebl/common/common.utils'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; import { FileUploadStatus, FileUploadType } from 'apps/api-gateway/src/enum'; import { AwsService } from '@credebl/aws'; import { io } from 'socket.io-client'; import { IIssuedCredentialSearchParams, IssueCredentialType } from 'apps/api-gateway/src/issuance/interfaces'; -import { - ICredentialOfferResponse, - IDeletedIssuanceRecords, - IIssuedCredential, - IJsonldCredential, - IPrettyVc, - ISchemaObject -} from '@credebl/common/interfaces/issuance.interface'; +import { ICredentialOfferResponse, IDeletedIssuanceRecords, IIssuedCredential, IJsonldCredential, IPrettyVc, ISchemaObject } from '@credebl/common/interfaces/issuance.interface'; import { OOBIssueCredentialDto } from 'apps/api-gateway/src/issuance/dtos/issuance.dto'; -import { RecordType, agent_invitations, organisation, user } from '@prisma/client'; -import { - createOobJsonldIssuancePayload, - validateAndUpdateIssuanceDates, - validateEmail -} from '@credebl/common/cast.helper'; +import { RecordType, agent_invitations, org_agents, organisation, user } from '@prisma/client'; +import { createOobJsonldIssuancePayload, validateAndUpdateIssuanceDates, validateEmail } from '@credebl/common/cast.helper'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; import * as pLimit from 'p-limit'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { validateW3CSchemaAttributes } from '../libs/helpers/attributes.validator'; import { ISchemaDetail } from '@credebl/common/interfaces/schema.interface'; -import ContextStorageService, { ContextStorageServiceKey } from '@credebl/context/contextStorageService.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { extractAttributeNames, unflattenCsvRow } from '../libs/helpers/attributes.extractor'; -import { redisStore } from 'cache-manager-ioredis-yet'; -import { EmailService } from '@credebl/common/email.service'; @Injectable() export class IssuanceService { @@ -104,23 +48,19 @@ export class IssuanceService { private readonly commonService: CommonService, private readonly issuanceRepository: IssuanceRepository, private readonly userActivityRepository: UserActivityRepository, - @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, + @Inject(CACHE_MANAGER) private cacheManager: Cache, private readonly outOfBandIssuance: OutOfBandIssuance, private readonly emailData: EmailDto, private readonly awsService: AwsService, - @InjectQueue('bulk-issuance') private readonly bulkIssuanceQueue: Queue, - // TODO: Remove duplicate, unused variable - @Inject(CACHE_MANAGER) private readonly cacheService: Cache, - @Inject(ContextStorageServiceKey) - private readonly contextStorageService: ContextStorageService, - private readonly natsClient: NATSClient, - private readonly emailService: EmailService - ) {} + @InjectQueue('bulk-issuance') private bulkIssuanceQueue: Queue, + @Inject(CACHE_MANAGER) private cacheService: Cache + ) { } async getIssuanceRecords(orgId: string): Promise { try { return await this.issuanceRepository.getIssuanceRecordsCount(orgId); } catch (error) { + this.logger.error( `[getIssuanceRecords ] [NATS call]- error in get issuance records count : ${JSON.stringify(error)}` ); @@ -129,34 +69,28 @@ export class IssuanceService { } async getW3CSchemaAttributes(schemaUrl: string): Promise { - this.logger.debug('Getting w3c schema attributes from schemaUrl', schemaUrl); const schemaRequest = await this.commonService.httpGet(schemaUrl).then(async (response) => response); if (!schemaRequest) { throw new NotFoundException(ResponseMessages.schema.error.W3CSchemaNotFOund, { cause: new Error(), description: ResponseMessages.errorMessages.notFound }); - } - - const getSchemaDetails = await this.issuanceRepository.getSchemaDetails(schemaUrl); - - if (!getSchemaDetails) { - throw new NotFoundException(ResponseMessages.schema.error.notFound); - } + } - const schemaAttributes = JSON.parse(getSchemaDetails?.attributes); - this.logger.debug('Schema attributes fetched successfully', JSON.stringify(schemaAttributes, null, 2)); + const getSchemaDetails = await this.issuanceRepository.getSchemaDetails(schemaUrl); + const schemaAttributes = JSON.parse(getSchemaDetails?.attributes); - return schemaAttributes; + return schemaAttributes; } async sendCredentialCreateOffer(payload: IIssuance): Promise { try { - const { orgId, credentialDefinitionId, comment, credentialData, isValidateSchema } = payload || {}; + const { orgId, credentialDefinitionId, comment, credentialData } = payload || {}; if (payload.credentialType === IssueCredentialType.INDY) { - const schemaResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); + const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); if (schemaResponse?.attributes) { const schemaResponseError = []; const attributesArray: IAttributes[] = JSON.parse(schemaResponse.attributes); @@ -190,12 +124,14 @@ export class IssuanceService { throw new NotFoundException(ResponseMessages.issuance.error.orgAgentTypeNotFound); } - const url = await getAgentUrl(agentEndPoint, CommonConstants.CREATE_OFFER); + const issuanceMethodLabel = 'create-offer'; + const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId); + if (payload.credentialType === IssueCredentialType.JSONLD) { await validateAndUpdateIssuanceDates(credentialData); } - + const issuancePromises = credentialData.map(async (credentials) => { const { connectionId, attributes, credential, options } = credentials; let issueData; @@ -215,21 +151,6 @@ export class IssuanceService { comment }; } else if (payload.credentialType === IssueCredentialType.JSONLD) { - const schemaIds = credentialData?.map((item) => { - const context: string[] = item?.credential?.['@context']; - return Array.isArray(context) && 1 < context.length ? context[1] : undefined; - }); - - const schemaDetails = await this._getSchemaDetails(schemaIds); - - const ledgerIds = schemaDetails?.map((item) => item?.ledgerId); - - for (const ledgerId of ledgerIds) { - if (agentDetails?.ledgerId !== ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); - } - } - issueData = { protocolVersion: payload.protocolVersion || 'v2', connectionId, @@ -250,10 +171,7 @@ export class IssuanceService { const schemaServerUrl = issueData?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); - - if (isValidateSchema) { - validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes, this.logger); - } + validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); } await this.delay(500); @@ -287,18 +205,6 @@ export class IssuanceService { if (allSuccessful) { finalStatusCode = HttpStatus.CREATED; - const context = payload?.credentialData[0]?.credential?.['@context'] as string[]; - - if (Array.isArray(context) && context.includes(CommonConstants.W3C_SCHEMA_URL)) { - const filterData = context.filter((item) => CommonConstants.W3C_SCHEMA_URL !== item); - const [schemaId] = filterData; - results.forEach((record) => { - if (PromiseResult.FULFILLED === record.status && record?.value?.threadId) { - this.issuanceRepository.updateSchemaIdByThreadId(record?.value?.threadId, schemaId); - } - }); - } - finalMessage = ResponseMessages.issuance.success.create; } else if (allFailed) { finalStatusCode = HttpStatus.BAD_REQUEST; @@ -315,6 +221,7 @@ export class IssuanceService { }; return finalResult; + } catch (error) { this.logger.error(`[sendCredentialCreateOffer] - error in create credentials : ${JSON.stringify(error)}`); const errorStack = error?.status?.message?.error?.reason || error?.status?.message?.error; @@ -333,22 +240,12 @@ export class IssuanceService { async sendCredentialOutOfBand(payload: OOBIssueCredentialDto): Promise<{ response: object }> { try { - const { - orgId, - credentialDefinitionId, - comment, - attributes, - protocolVersion, - credential, - options, - credentialType, - isShortenUrl, - reuseConnection, - isValidateSchema - } = payload; + + const { orgId, credentialDefinitionId, comment, attributes, protocolVersion, credential, options, credentialType, isShortenUrl, reuseConnection } = payload; if (credentialType === IssueCredentialType.INDY) { - const schemadetailsResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); + const schemadetailsResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); if (schemadetailsResponse?.attributes) { const schemadetailsResponseError = []; @@ -356,7 +253,8 @@ export class IssuanceService { attributesArray.forEach((attribute) => { if (attribute.attributeName && attribute.isRequired) { - payload.attributes.forEach((attr) => { + + payload.attributes.map((attr) => { if (attr.name === attribute.attributeName && attribute.isRequired && !attr.value) { schemadetailsResponseError.push( `Attribute '${attribute.attributeName}' is required but has an empty value.` @@ -369,17 +267,15 @@ export class IssuanceService { if (0 < schemadetailsResponseError.length) { throw new BadRequestException(schemadetailsResponseError); } + } } const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); let invitationDid: string | undefined; if (true === reuseConnection) { - const data: agent_invitations[] = await this.issuanceRepository.getInvitationDidByOrgId(orgId); - if (data && 0 < data.length) { - const [firstElement] = data; - invitationDid = firstElement?.invitationDid ?? undefined; - } + const invitation: agent_invitations = await this.issuanceRepository.getInvitationDidByOrgId(orgId); + invitationDid = invitation?.invitationDid ?? undefined; } const { agentEndPoint, organisation } = agentDetails; @@ -387,16 +283,21 @@ export class IssuanceService { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = await getAgentUrl(agentEndPoint, CommonConstants.CREATE_OFFER_OUT_OF_BAND); + const orgAgentType = await this.issuanceRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + + const issuanceMethodLabel = 'create-offer-oob'; + const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId); + let issueData; if (credentialType === IssueCredentialType.INDY) { + issueData = { protocolVersion: protocolVersion || 'v1', credentialFormats: { indy: { // eslint-disable-next-line @typescript-eslint/no-unused-vars - attributes: attributes.map(({ isRequired, ...rest }) => rest), + attributes: (attributes).map(({ isRequired, ...rest }) => rest), credentialDefinitionId } }, @@ -407,25 +308,12 @@ export class IssuanceService { imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, label: organisation?.name, comment: comment || '', - invitationDid: invitationDid || undefined + invitationDid:invitationDid || undefined }; + } if (credentialType === IssueCredentialType.JSONLD) { - const context = credential?.['@context'][1]; - - const schemaDetails = await this.issuanceRepository.getSchemaDetails(String(context)); - - if (!schemaDetails) { - throw new NotFoundException(ResponseMessages.schema.error.notFound); - } - - const ledgerId = schemaDetails?.ledgerId; - - if (agentDetails?.ledgerId !== ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); - } - issueData = { protocolVersion: protocolVersion || 'v2', credentialFormats: { @@ -441,7 +329,7 @@ export class IssuanceService { imageUrl: organisation?.logoUrl || payload?.imageUrl || undefined, label: organisation?.name, comment: comment || '', - invitationDid: invitationDid || undefined + invitationDid:invitationDid || undefined }; const payloadAttributes = issueData?.credentialFormats?.jsonld?.credential?.credentialSubject; @@ -451,10 +339,8 @@ export class IssuanceService { const schemaServerUrl = issueData?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); - - if (isValidateSchema) { - validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes, this.logger); - } + validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); + } const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(issueData, url, orgId); if (isShortenUrl) { @@ -475,6 +361,7 @@ export class IssuanceService { message: errorStack?.reason ? errorStack?.reason : errorStack?.message, statusCode: error?.status?.code }); + } else { throw new RpcException(error.response ? error.response : error); } @@ -483,37 +370,33 @@ export class IssuanceService { async storeIssuanceObjectReturnUrl(storeObj: string): Promise { try { - // Set default to false, since currently our invitation are not multi-use - const persistent: boolean = false; - //nats call in agent-service to create an invitation url - const pattern = { cmd: 'store-object-return-url' }; - const payload = { persistent, storeObj }; - const message = await this.natsCall(pattern, payload); - return message.response; - } catch (error) { - this.logger.error( - `[storeIssuanceObjectReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify(error)}` - ); - throw error; - } + // Set default to false, since currently our invitation are not multi-use + const persistent: boolean = false; + //nats call in agent-service to create an invitation url + const pattern = { cmd: 'store-object-return-url' }; + const payload = { persistent, storeObj }; + const message = await this.natsCall(pattern, payload); + return message.response; + } catch (error) { + this.logger.error(`[storeIssuanceObjectReturnUrl] [NATS call]- error in storing object and returning url : ${JSON.stringify(error)}`); + throw error; + } } // Created this function to avoid the impact of actual "natsCall" function for other operations // Once implement this for all component then we'll remove the duplicate function async natsCallAgent(pattern: IPattern, payload: ISendOfferNatsPayload): Promise { try { - const createOffer = await this.natsClient - .send(this.issuanceServiceProxy, pattern, payload) - - .catch((error) => { + const createOffer = await this.issuanceServiceProxy + .send(pattern, payload) + .toPromise() + .catch(error => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.message - }, - error.error - ); + }, error.error); }); return createOffer; } catch (error) { @@ -522,30 +405,25 @@ export class IssuanceService { } } - async natsCall( - pattern: object, - payload: object - ): Promise<{ + async natsCall(pattern: object, payload: object): Promise<{ response: string; }> { try { return this.issuanceServiceProxy - .send(pattern, payload) + .send(pattern, payload) .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.message - }, - error.error - ); + }, error.error); }); } catch (error) { this.logger.error(`[natsCall] - error in nats call : ${JSON.stringify(error)}`); @@ -559,9 +437,7 @@ export class IssuanceService { const payload: ISendOfferNatsPayload = { issueData, url, orgId }; return await this.natsCallAgent(pattern, payload); } catch (error) { - this.logger.error( - `[_sendCredentialCreateOffer] [NATS call]- error in create credentials : ${JSON.stringify(error)}` - ); + this.logger.error(`[_sendCredentialCreateOffer] [NATS call]- error in create credentials : ${JSON.stringify(error)}`); throw error; } } @@ -572,20 +448,10 @@ export class IssuanceService { issuedCredentialsSearchCriteria: IIssuedCredentialSearchParams ): Promise { try { - let schemaIds; - if (issuedCredentialsSearchCriteria?.search) { - const schemaDetails = await this._getSchemaDetailsByName(issuedCredentialsSearchCriteria?.search, orgId); - - if (schemaDetails && 0 < schemaDetails?.length) { - schemaIds = schemaDetails.map((item) => item?.schemaLedgerId); - } - } - const getIssuedCredentialsList = await this.issuanceRepository.getAllIssuedCredentials( user, orgId, - issuedCredentialsSearchCriteria, - schemaIds + issuedCredentialsSearchCriteria ); const getSchemaIds = getIssuedCredentialsList?.issuedCredentialsList?.map((schema) => schema?.schemaId); @@ -594,17 +460,17 @@ export class IssuanceService { let responseWithSchemaName; if (getSchemaDetails) { - responseWithSchemaName = getIssuedCredentialsList?.issuedCredentialsList.map((file) => { - const schemaDetail = getSchemaDetails?.find((schema) => schema.schemaLedgerId === file.schemaId); + responseWithSchemaName = getIssuedCredentialsList?.issuedCredentialsList.map(file => { + const schemaDetail = getSchemaDetails?.find(schema => schema.schemaLedgerId === file.schemaId); return { ...file, schemaName: schemaDetail?.name }; }); - } else { + } else { const getSchemaUrlDetails = await this.getSchemaUrlDetails(getSchemaIds); - responseWithSchemaName = getIssuedCredentialsList?.issuedCredentialsList.map((file) => { - const schemaDetail = getSchemaUrlDetails?.find((schema) => schema.title); + responseWithSchemaName = getIssuedCredentialsList?.issuedCredentialsList.map(file => { + const schemaDetail = getSchemaUrlDetails?.find(schema => schema.title); return { ...file, schemaName: schemaDetail?.title @@ -614,8 +480,7 @@ export class IssuanceService { const issuedCredentialsResponse: IIssuedCredential = { totalItems: getIssuedCredentialsList.issuedCredentialsCount, hasNextPage: - issuedCredentialsSearchCriteria.pageSize * issuedCredentialsSearchCriteria.pageNumber < - getIssuedCredentialsList.issuedCredentialsCount, + issuedCredentialsSearchCriteria.pageSize * issuedCredentialsSearchCriteria.pageNumber < getIssuedCredentialsList.issuedCredentialsCount, hasPreviousPage: 1 < issuedCredentialsSearchCriteria.pageNumber, nextPage: Number(issuedCredentialsSearchCriteria.pageNumber) + 1, previousPage: issuedCredentialsSearchCriteria.pageNumber - 1, @@ -634,46 +499,23 @@ export class IssuanceService { } } - async _getSchemaDetailsByName(schemaName: string, orgId: string): Promise { - const pattern = { cmd: 'get-schemas-details-by-name' }; - const payload = { schemaName, orgId }; - const schemaDetails = await this.natsClient - .send(this.issuanceServiceProxy, pattern, payload) - - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - - return schemaDetails; - } - async getSchemaUrlDetails(schemaUrls: string[]): Promise { const results = []; - + for (const schemaUrl of schemaUrls) { - const schemaRequest = await this.commonService.httpGet(schemaUrl); - if (!schemaRequest) { - throw new NotFoundException(ResponseMessages.schema.error.W3CSchemaNotFOund, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - results.push(schemaRequest); + const schemaRequest = await this.commonService.httpGet(schemaUrl); + if (!schemaRequest) { + throw new NotFoundException(ResponseMessages.schema.error.W3CSchemaNotFOund, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + results.push(schemaRequest); } return results; - } +} - async _getIssueCredentials( - url: string, - apiKey: string - ): Promise<{ + async _getIssueCredentials(url: string, apiKey: string): Promise<{ response: string; }> { try { @@ -686,12 +528,9 @@ export class IssuanceService { } } - async getIssueCredentialsbyCredentialRecordId( - user: IUserRequest, - credentialRecordId: string, - orgId: string - ): Promise { + async getIssueCredentialsbyCredentialRecordId(user: IUserRequest, credentialRecordId: string, orgId: string): Promise { try { + const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); // const platformConfig: platform_config = await this.issuanceRepository.getPlatformConfigDetails(); @@ -700,266 +539,159 @@ export class IssuanceService { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } - const url = await getAgentUrl(agentEndPoint, CommonConstants.GET_OFFER_BY_CRED_ID, credentialRecordId); + const orgAgentType = await this.issuanceRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + const issuanceMethodLabel = 'get-issue-credential-by-credential-id'; + const url = await this.getAgentUrl(issuanceMethodLabel, orgAgentType, agentEndPoint, agentDetails?.tenantId, credentialRecordId); + const createConnectionInvitation = await this._getIssueCredentialsbyCredentialRecordId(url, orgId); return createConnectionInvitation?.response; } catch (error) { - this.logger.error( - `[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}` - ); - if (error?.status?.message?.error) { + this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, + statusCode: error?.status?.code }); + + } else { + throw new RpcException(error.response ? error.response : error); } - throw new RpcException(error.response || error); } } - async getIssueCredentialWebhook(payload: IssueCredentialWebhookPayload): Promise { + async getIssueCredentialWebhook(payload: IssueCredentialWebhookPayload): Promise { try { - const agentDetails = await this.issuanceRepository.saveIssuedCredentialDetails(payload); + const agentDetails: org_agents = await this.issuanceRepository.saveIssuedCredentialDetails(payload); return agentDetails; } catch (error) { - this.logger.error( - `[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}` - ); + this.logger.error(`[getIssueCredentialsbyCredentialRecordId] - error in get credentials : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async _getIssueCredentialsbyCredentialRecordId( - url: string, - orgId: string - ): Promise<{ + async _getIssueCredentialsbyCredentialRecordId(url: string, orgId: string): Promise<{ response: string; }> { try { const pattern = { cmd: 'agent-get-issued-credentials-by-credentialDefinitionId' }; const payload = { url, orgId }; return await this.natsCall(pattern, payload); + } catch (error) { - this.logger.error( - `[_getIssueCredentialsbyCredentialRecordId] [NATS call]- error in fetch credentials : ${JSON.stringify(error)}` - ); + this.logger.error(`[_getIssueCredentialsbyCredentialRecordId] [NATS call]- error in fetch credentials : ${JSON.stringify(error)}`); throw error; } } - async outOfBandCredentialOffer( - outOfBandCredential: OutOfBandCredentialOfferPayload, - platformName?: string, - organizationLogoUrl?: string, - prettyVc?: IPrettyVc - ): Promise { - try { - const { - credentialOffer, - comment, - credentialDefinitionId, - orgId, - protocolVersion, - attributes, - emailId, - credentialType, - isReuseConnection, - isValidateSchema - } = outOfBandCredential; - - this.logger.debug('Request reaced isuance microservice service'); - const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - - this.logger.debug('The credential to be issued is of type:', credentialType); - if (IssueCredentialType.JSONLD === credentialType) { - await validateAndUpdateIssuanceDates(credentialOffer); - - this.logger.debug('Validated/Updated Issuance dates credential offer'); - const schemaIds = credentialOffer?.map((item) => { - const context: string[] = item?.credential?.['@context']; - return Array.isArray(context) && 1 < context.length ? context[1] : undefined; - }); +async outOfBandCredentialOffer(outOfBandCredential: OutOfBandCredentialOfferPayload, platformName?: string, organizationLogoUrl?: string, prettyVc?: IPrettyVc): Promise { + try { + const { + credentialOffer, + comment, + credentialDefinitionId, + orgId, + protocolVersion, + attributes, + emailId, + credentialType, + isReuseConnection + } = outOfBandCredential; - this.logger.debug('Issuing credential with schemaIds', schemaIds); - const schemaDetails = await this._getSchemaDetails(schemaIds); + if (IssueCredentialType.JSONLD === credentialType) { + await validateAndUpdateIssuanceDates(credentialOffer); + } - const ledgerIds = schemaDetails?.map((item) => item?.ledgerId); - this.logger.debug('Issuance will be done with the following schemas: ', JSON.stringify(schemaDetails, null, 2)); + if (IssueCredentialType.INDY === credentialType) { + const schemaResponse: SchemaDetails = await this.issuanceRepository.getCredentialDefinitionDetails( + credentialDefinitionId + ); - for (const ledgerId of ledgerIds) { - this.logger.debug('Checking ledger compatibility of schemas and issuer agent'); - if (agentDetails?.ledgerId !== ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); - } - } + let attributesArray: IAttributes[] = []; + if (schemaResponse?.attributes) { + attributesArray = JSON.parse(schemaResponse.attributes); } - if (IssueCredentialType.INDY === credentialType) { - const schemaResponse: SchemaDetails = - await this.issuanceRepository.getCredentialDefinitionDetails(credentialDefinitionId); - - this.logger.debug( - 'Schema details for indy based credential received:', - JSON.stringify(schemaResponse, null, 2) - ); - let attributesArray: IAttributes[] = []; - if (schemaResponse?.attributes) { - attributesArray = JSON.parse(schemaResponse.attributes); - } - - if (0 < attributes?.length) { - const attrError = []; - attributesArray.forEach((schemaAttribute, i) => { - if (schemaAttribute.isRequired) { - const attribute = attributes.find((attribute) => attribute.name === schemaAttribute.attributeName); - if (!attribute?.value) { - attrError.push(`attributes.${i}.Attribute ${schemaAttribute.attributeName} is required`); - } + if (0 < attributes?.length) { + const attrError = []; + attributesArray.forEach((schemaAttribute, i) => { + if (schemaAttribute.isRequired) { + const attribute = attributes.find((attribute) => attribute.name === schemaAttribute.attributeName); + if (!attribute?.value) { + attrError.push(`attributes.${i}.Attribute ${schemaAttribute.attributeName} is required`); } - }); - if (0 < attrError.length) { - this.logger.debug('Error validating attributes. Number of errors:', attrError.length); - throw new BadRequestException(attrError); - } - } - if (0 < credentialOffer?.length) { - const credefError = []; - credentialOffer.forEach((credentialAttribute, index) => { - attributesArray.forEach((schemaAttribute, i) => { - const attribute = credentialAttribute.attributes.find( - (attribute) => attribute.name === schemaAttribute.attributeName - ); - - if (schemaAttribute.isRequired && !attribute?.value) { - credefError.push( - `credentialOffer.${index}.attributes.${i}.Attribute ${schemaAttribute.attributeName} is required` - ); - } - }); - }); - if (0 < credefError.length) { - throw new BadRequestException(credefError); } + }); + if (0 < attrError.length) { + throw new BadRequestException(attrError); } } + if (0 < credentialOffer?.length) { + const credefError = []; + credentialOffer.forEach((credentialAttribute, index) => { + attributesArray.forEach((schemaAttribute, i) => { + const attribute = credentialAttribute.attributes.find( + (attribute) => attribute.name === schemaAttribute.attributeName + ); - const { organisation } = agentDetails; - if (!agentDetails) { - throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); - } - - const url = await getAgentUrl(agentDetails.agentEndPoint, CommonConstants.CREATE_OFFER_OUT_OF_BAND); - const organizationDetails = await this.issuanceRepository.getOrganization(orgId); - - if (!organizationDetails) { - throw new NotFoundException(ResponseMessages.issuance.error.organizationNotFound); - } - const errors = []; - this.logger.debug('Creating offer response for agent on url: ', url); - let credentialOfferResponse; - const arraycredentialOfferResponse = []; - const sendEmailCredentialOffer: { - iterator: CredentialOffer; - emailId: string; - index: number; - credentialType: IssueCredentialType; - protocolVersion: string; - isReuseConnection?: boolean; - attributes: IAttributes[]; - credentialDefinitionId: string; - outOfBandCredential: OutOfBandCredentialOfferPayload; - comment: string; - organisation: organisation; - errors: string[]; - url: string; - orgId: string; - organizationDetails: organisation; - platformName?: string; - organizationLogoUrl?: string; - prettyVc?: IPrettyVc; - isValidateSchema?: boolean; - } = { - credentialType, - protocolVersion, - isReuseConnection, - attributes, - credentialDefinitionId, - outOfBandCredential, - comment, - organisation, - errors, - url, - orgId, - isValidateSchema, - organizationDetails, - iterator: undefined, - emailId: emailId || '', - index: 0, - platformName: platformName || null, - organizationLogoUrl: organizationLogoUrl || null, - prettyVc: { - certificate: prettyVc?.certificate, - size: prettyVc?.size, - orientation: prettyVc?.orientation, - height: prettyVc?.height, - width: prettyVc?.width - } - }; - - if (credentialOffer) { - this.logger.debug('Iterating over credentials offers: ', credentialOffer.entries()); - for (const [index, iterator] of credentialOffer.entries()) { - sendEmailCredentialOffer['iterator'] = iterator; - sendEmailCredentialOffer['emailId'] = iterator.emailId; - sendEmailCredentialOffer['index'] = index; - - await this.delay(500); // Wait for 0.5 seconds - this.logger.debug(`Sending offer number: index: ${index}, iterator: ${iterator}`); - const sendOobOffer = await this.sendEmailForCredentialOffer(sendEmailCredentialOffer); - arraycredentialOfferResponse.push(sendOobOffer); - } - if (0 < errors.length) { - throw errors; - } - - return arraycredentialOfferResponse.every((result) => true === result); - } else { - this.logger.debug('Sending a single OOB email offer'); - credentialOfferResponse = await this.sendEmailForCredentialOffer(sendEmailCredentialOffer); - return credentialOfferResponse; - } - } catch (error) { - this.logger.error( - `[outOfBoundCredentialOffer] - error in create out-of-band credentials: ${JSON.stringify(error)}` - ); - if (0 < error?.length) { - const errorStack = error?.map((item) => { - const { statusCode, message, error } = item?.error || item?.response || {}; - return { - statusCode, - message, - error - }; - }); - throw new RpcException({ - error: errorStack, - statusCode: error?.status?.code, - message: ResponseMessages.issuance.error.unableToCreateOOBOffer + if (schemaAttribute.isRequired && !attribute?.value) { + credefError.push( + `credentialOffer.${index}.attributes.${i}.Attribute ${schemaAttribute.attributeName} is required` + ); + } + }); }); - } else { - throw new RpcException(error.response ? error.response : error); + if (0 < credefError.length) { + throw new BadRequestException(credefError); + } } } - } + const agentDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialOffer): Promise { - const { - iterator, - emailId, - index, + const { organisation } = agentDetails; + if (!agentDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); + } + const orgAgentType = await this.issuanceRepository.getOrgAgentType(agentDetails?.orgAgentTypeId); + + const issuanceMethodLabel = 'create-offer-oob'; + const url = await this.getAgentUrl( + issuanceMethodLabel, + orgAgentType, + agentDetails.agentEndPoint, + agentDetails.tenantId + ); + const organizationDetails = await this.issuanceRepository.getOrganization(orgId); + + if (!organizationDetails) { + throw new NotFoundException(ResponseMessages.issuance.error.organizationNotFound); + } + const errors = []; + let credentialOfferResponse; + const arraycredentialOfferResponse = []; + const sendEmailCredentialOffer: { + iterator: CredentialOffer; + emailId: string; + index: number; + credentialType: IssueCredentialType; + protocolVersion: string; + isReuseConnection?: boolean; + attributes: IAttributes[]; + credentialDefinitionId: string; + outOfBandCredential: OutOfBandCredentialOfferPayload; + comment: string; + organisation: organisation; + errors: string[]; + url: string; + orgId: string; + organizationDetails: organisation; + platformName?: string; + organizationLogoUrl?: string; + prettyVc?: IPrettyVc; + } = { credentialType, protocolVersion, + isReuseConnection, attributes, credentialDefinitionId, outOfBandCredential, @@ -969,273 +701,362 @@ export class IssuanceService { url, orgId, organizationDetails, - platformName, - organizationLogoUrl, - isReuseConnection, - isValidateSchema - } = sendEmailCredentialOffer; - const iterationNo = index + 1; - try { - let invitationDid: string | undefined; - if (true === isReuseConnection) { - this.logger.debug('This is a reuse connection, fetching invitation did'); - const data: agent_invitations[] = await this.issuanceRepository.getInvitationDidByOrgId(orgId); - if (data && 0 < data.length) { - const [firstElement] = data; - invitationDid = firstElement?.invitationDid ?? undefined; - } + iterator: undefined, + emailId: emailId || '', + index: 0, + platformName: platformName || null, + organizationLogoUrl: organizationLogoUrl || null, + prettyVc: { + certificate: prettyVc?.certificate, + size: prettyVc?.size, + orientation: prettyVc?.orientation } + }; - let outOfBandIssuancePayload; - this.logger.debug('This is an email issuance of type', credentialType); - if (IssueCredentialType.INDY === credentialType) { - outOfBandIssuancePayload = { - protocolVersion: protocolVersion || 'v1', - credentialFormats: { - indy: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - attributes: attributes ? attributes : iterator.attributes.map(({ isRequired, ...rest }) => rest), - credentialDefinitionId - } - }, - autoAcceptCredential: outOfBandCredential.autoAcceptCredential || 'always', - comment, - goalCode: outOfBandCredential.goalCode || undefined, - parentThreadId: outOfBandCredential.parentThreadId || undefined, - willConfirm: outOfBandCredential.willConfirm || undefined, - label: organisation?.name, - imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl, - invitationDid: invitationDid || undefined - }; - } + if (credentialOffer) { - if (IssueCredentialType.JSONLD === credentialType) { - outOfBandIssuancePayload = { - protocolVersion: 'v2', - credentialFormats: { - jsonld: { - credential: iterator.credential, - options: iterator.options - } - }, - // For Educreds - autoAcceptCredential: AutoAccept.Always, - comment, - goalCode: outOfBandCredential.goalCode || undefined, - parentThreadId: outOfBandCredential.parentThreadId || undefined, - willConfirm: outOfBandCredential.willConfirm || undefined, - label: organisation?.name, - imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl, - invitationDid: invitationDid || undefined + for (const [index, iterator] of credentialOffer.entries()) { + sendEmailCredentialOffer['iterator'] = iterator; + sendEmailCredentialOffer['emailId'] = iterator.emailId; + sendEmailCredentialOffer['index'] = index; + + await this.delay(500); // Wait for 0.5 seconds + const sendOobOffer = await this.sendEmailForCredentialOffer(sendEmailCredentialOffer); + + arraycredentialOfferResponse.push(sendOobOffer); + } + if (0 < errors.length) { + throw errors; + } + + return arraycredentialOfferResponse.every((result) => true === result); + } else { + credentialOfferResponse = await this.sendEmailForCredentialOffer(sendEmailCredentialOffer); + return credentialOfferResponse; + } + } catch (error) { + this.logger.error( + `[outOfBoundCredentialOffer] - error in create out-of-band credentials: ${JSON.stringify(error)}` + ); + if (0 < error?.length) { + const errorStack = error?.map((item) => { + const { statusCode, message, error } = item?.error || item?.response || {}; + return { + statusCode, + message, + error }; + }); + throw new RpcException({ + error: errorStack, + statusCode: error?.status?.code, + message: ResponseMessages.issuance.error.unableToCreateOOBOffer + }); + } else { + throw new RpcException(error.response ? error.response : error); + } + } +} - const payloadAttributes = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.credentialSubject; +async sendEmailForCredentialOffer(sendEmailCredentialOffer: SendEmailCredentialOffer): Promise { + const { + iterator, + emailId, + index, + credentialType, + protocolVersion, + attributes, + credentialDefinitionId, + outOfBandCredential, + comment, + organisation, + errors, + url, + orgId, + organizationDetails, + platformName, + organizationLogoUrl, + isReuseConnection + } = sendEmailCredentialOffer; + const iterationNo = index + 1; + try { + + + let invitationDid: string | undefined; + if (true === isReuseConnection) { + const invitation: agent_invitations = await this.issuanceRepository.getInvitationDidByOrgId(orgId); + invitationDid = invitation?.invitationDid ?? undefined; + } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { id, ...filteredIssuanceAttributes } = payloadAttributes; + let outOfBandIssuancePayload; + if (IssueCredentialType.INDY === credentialType) { + + outOfBandIssuancePayload = { + protocolVersion: protocolVersion || 'v1', + credentialFormats: { + indy: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + attributes: attributes ? attributes : iterator.attributes.map(({ isRequired, ...rest }) => rest), + credentialDefinitionId + } + }, + autoAcceptCredential: outOfBandCredential.autoAcceptCredential || 'always', + comment, + goalCode: outOfBandCredential.goalCode || undefined, + parentThreadId: outOfBandCredential.parentThreadId || undefined, + willConfirm: outOfBandCredential.willConfirm || undefined, + label: organisation?.name, + imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl, + invitationDid: invitationDid || undefined + }; + } - const schemaServerUrl = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; + if (IssueCredentialType.JSONLD === credentialType) { + outOfBandIssuancePayload = { + protocolVersion: 'v2', + credentialFormats: { + jsonld: { + credential: iterator.credential, + options: iterator.options + } + }, + // For Educreds + autoAcceptCredential: AutoAccept.Always, + comment, + goalCode: outOfBandCredential.goalCode || undefined, + parentThreadId: outOfBandCredential.parentThreadId || undefined, + willConfirm: outOfBandCredential.willConfirm || undefined, + label: organisation?.name, + imageUrl: organisation?.logoUrl || outOfBandCredential?.imageUrl, + invitationDid: invitationDid || undefined + }; - const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); + const payloadAttributes = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.credentialSubject; - if (isValidateSchema) { - validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes, this.logger); - } - } - this.logger.debug('Payload created for issuance'); - const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, orgId); - this.logger.debug('Offer created successfully'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...filteredIssuanceAttributes } = payloadAttributes; - if (!credentialCreateOfferDetails) { - errors.push(new NotFoundException(ResponseMessages.issuance.error.credentialOfferNotFound)); - return false; - } + const schemaServerUrl = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.['@context']?.[1]; - const invitationUrl: string = credentialCreateOfferDetails.response?.invitationUrl; - this.logger.debug('Shortening invitation url'); - const shortenUrl: string = await this.storeIssuanceObjectReturnUrl(invitationUrl); + const schemaUrlAttributes = await this.getW3CSchemaAttributes(schemaServerUrl); + validateW3CSchemaAttributes(filteredIssuanceAttributes, schemaUrlAttributes); - const deepLinkURL = convertUrlToDeepLinkUrl(shortenUrl); - this.logger.debug('Deeplink URL created successfully'); + } + const credentialCreateOfferDetails = await this._outOfBandCredentialOffer(outOfBandIssuancePayload, url, orgId); - if (!invitationUrl) { - errors.push(new NotFoundException(ResponseMessages.issuance.error.invitationNotFound)); - return false; - } - const qrCodeOptions = { type: 'image/png' }; - const outOfBandIssuanceQrCode = await QRCode.toDataURL(shortenUrl, qrCodeOptions); - const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); - if (!platformConfigData) { - errors.push(new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound)); - return false; - } - this.emailData.emailFrom = platformConfigData?.emailFrom; - this.emailData.emailTo = iterator?.emailId ?? emailId; - const platform = platformName || process.env.PLATFORM_NAME; - this.emailData.emailSubject = `${platform} Platform: Issuance of Your Credential`; - this.emailData.emailHtml = this.outOfBandIssuance.outOfBandIssuance( - emailId, - organizationDetails.name, - deepLinkURL, - platformName, - organizationLogoUrl - ); - this.emailData.emailAttachments = [ - { - filename: 'qrcode.png', - content: outOfBandIssuanceQrCode.split(';base64,')[1], - contentType: 'image/png', - disposition: 'attachment' - } - ]; - this.logger.debug('Invitation url and deeplink created successfully. Sending email'); + if (!credentialCreateOfferDetails) { + errors.push(new NotFoundException(ResponseMessages.issuance.error.credentialOfferNotFound)); + return false; + } - const isEmailSent = await this.emailService.sendEmail(this.emailData); + const invitationUrl: string = credentialCreateOfferDetails.response?.invitationUrl; - this.logger.log(`isEmailSent ::: ${JSON.stringify(isEmailSent)}-${this.counter}`); - this.counter++; - if (!isEmailSent) { - errors.push(new InternalServerErrorException(ResponseMessages.issuance.error.emailSend)); - return false; - } + const shortenUrl: string = await this.storeIssuanceObjectReturnUrl(invitationUrl); - if (isEmailSent) { - const w3cSchemaId = outOfBandIssuancePayload?.credentialFormats?.jsonld?.credential?.['@context'] as string[]; - if (w3cSchemaId?.includes(CommonConstants.W3C_SCHEMA_URL)) { - const filterData = w3cSchemaId.filter((item) => CommonConstants.W3C_SCHEMA_URL !== item); - const [schemaId] = filterData; - if (credentialCreateOfferDetails.response.credentialRequestThId) { - this.issuanceRepository.updateSchemaIdByThreadId( - credentialCreateOfferDetails.response.credentialRequestThId, - schemaId - ); + const deeplLinkURL = convertUrlToDeepLinkUrl(shortenUrl); + + if (!invitationUrl) { + errors.push(new NotFoundException(ResponseMessages.issuance.error.invitationNotFound)); + return false; + } + const qrCodeOptions = { type: 'image/png' }; + const outOfBandIssuanceQrCode = await QRCode.toDataURL(shortenUrl, qrCodeOptions); + const platformConfigData = await this.issuanceRepository.getPlatformConfigDetails(); + if (!platformConfigData) { + errors.push(new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound)); + return false; + } + this.emailData.emailFrom = platformConfigData?.emailFrom; + this.emailData.emailTo = iterator?.emailId ?? emailId; + const platform = platformName || process.env.PLATFORM_NAME; + this.emailData.emailSubject = `${platform} Platform: Issuance of Your Credential`; + this.emailData.emailHtml = this.outOfBandIssuance.outOfBandIssuance(emailId, organizationDetails.name, deeplLinkURL, platformName, organizationLogoUrl); + this.emailData.emailAttachments = [ + { + filename: 'qrcode.png', + content: outOfBandIssuanceQrCode.split(';base64,')[1], + contentType: 'image/png', + disposition: 'attachment' } + ]; + + + const isEmailSent = await sendEmail(this.emailData); + + this.logger.log(`isEmailSent ::: ${JSON.stringify(isEmailSent)}-${this.counter}`); + this.counter++; + if (!isEmailSent) { + errors.push(new InternalServerErrorException(ResponseMessages.issuance.error.emailSend)); + return false; } - } - this.logger.debug( - 'Email sent successfully for credential threadId:', - credentialCreateOfferDetails.response.credentialRequestThId ?? '' - ); - return isEmailSent; - } catch (error) { - const iterationNoMessage = ` at position ${iterationNo}`; - this.logger.error('[OUT-OF-BAND CREATE OFFER - SEND EMAIL]::', JSON.stringify(error)); - const errorStack = error?.status?.message; - if (errorStack) { - errors.push( - new RpcException({ - statusCode: errorStack?.statusCode, - message: `${ResponseMessages.issuance.error.walletError} at position ${iterationNo}`, - error: `${errorStack?.error?.message} at position ${iterationNo}` - }) - ); + return isEmailSent; + + } catch (error) { + const iterationNoMessage = ` at position ${iterationNo}`; + this.logger.error('[OUT-OF-BAND CREATE OFFER - SEND EMAIL]::', JSON.stringify(error)); + const errorStack = error?.status?.message; + if (errorStack) { + errors.push( + new RpcException({ + statusCode: errorStack?.statusCode, + message: `${ResponseMessages.issuance.error.walletError} at position ${iterationNo}`, + error: `${errorStack?.error?.message} at position ${iterationNo}` + }) + ); - error.status.message = `${error.status.message}${iterationNoMessage}`; + error.status.message = `${error.status.message}${iterationNoMessage}`; throw error; - } else { - errors.push( - new RpcException({ - statusCode: error?.response?.statusCode, - message: `${error?.response?.message} at position ${iterationNo}`, - error: error?.response?.error - }) - ); - error.response.message = `${error.response.message}${iterationNoMessage}`; - throw error; // Check With other issuance flow - } + } else { + errors.push( + new RpcException({ + statusCode: error?.response?.statusCode, + message: `${error?.response?.message} at position ${iterationNo}`, + error: error?.response?.error + }) + ); + error.response.message = `${error.response.message}${iterationNoMessage}`; + throw error; // Check With other issuance flow } } +} - async _outOfBandCredentialOffer( - outOfBandIssuancePayload: object, - url: string, - orgId: string - ): Promise<{ + async _outOfBandCredentialOffer(outOfBandIssuancePayload: object, url: string, orgId: string): Promise<{ response; }> { try { - this.logger.debug('Issuance service call to the agent controller for creating an OOB offer'); const pattern = { cmd: 'agent-out-of-band-credential-offer' }; const payload = { outOfBandIssuancePayload, url, orgId }; - const result = await this.natsCall(pattern, payload); - this.logger.debug('Success: Issuance service call to the agent controller for creating an OOB offer'); - return result; + return await this.natsCall(pattern, payload); } catch (error) { this.logger.error(`[_outOfBandCredentialOffer] [NATS call]- error in out of band : ${JSON.stringify(error)}`); throw error; } } - async downloadBulkIssuanceCSVTemplate(orgId: string, templateDetails: TemplateDetailsInterface): Promise { + /** + * Description: Fetch agent url + * @param referenceId + * @returns agent URL + */ + async getAgentUrl( + issuanceMethodLabel: string, + orgAgentType: string, + agentEndPoint: string, + tenantId: string, + credentialRecordId?: string + ): Promise { try { - let schemaResponse: SchemaDetails; - let fileName: string; - - const orgDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - - const { schemaType, templateId } = templateDetails; - - if (!templateId) { - throw new BadRequestException(ResponseMessages.bulkIssuance.error.invalidtemplateId); - } - const timestamp = Math.floor(Date.now() / 1000); - - if (schemaType === SchemaType.INDY) { - schemaResponse = await this.issuanceRepository.getCredentialDefinitionDetails(templateId); - if (!schemaResponse) { - throw new NotFoundException(ResponseMessages.bulkIssuance.error.invalidIdentifier); + let url; + switch (issuanceMethodLabel) { + case 'create-offer': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_ISSUE_CREATE_CRED_OFFER_AFJ}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER}`.replace('#', tenantId) + : null; + break; } - const schemaDetails = await this.issuanceRepository.getSchemaDetails(schemaResponse.schemaLedgerId); - - if (!schemaDetails) { - throw new NotFoundException(ResponseMessages.schema.error.notFound); + case 'create-offer-oob': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_OUT_OF_BAND_CREDENTIAL_OFFER}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_CREATE_OFFER_OUT_OF_BAND}`.replace('#', tenantId) + : null; + break; } - if (orgDetails?.ledgerId !== schemaDetails?.ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); + case 'get-issue-credentials': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS}`.replace('#', tenantId) + : null; + break; } - fileName = `${schemaResponse.tag}-${timestamp}.csv`; - } else if (schemaType === SchemaType.W3C_Schema) { - const schemaDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier(templateId); + case 'get-issue-credential-by-credential-id': { - if (orgDetails?.ledgerId !== schemaDetails?.ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_ISSUE_GET_CREDS_AFJ_BY_CRED_REC_ID}/${credentialRecordId}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_CREDENTIALS_BY_CREDENTIAL_ID}`.replace('#', credentialRecordId).replace('@', tenantId) + : null; + break; } - if (!schemaDetails) { - throw new NotFoundException(ResponseMessages.bulkIssuance.error.invalidIdentifier); + default: { + break; } - const { attributes, schemaLedgerId, name } = schemaDetails; - schemaResponse = { attributes, schemaLedgerId, name }; - fileName = `${schemaResponse.name}-${timestamp}.csv`; } - const attributesArray = JSON.parse(schemaResponse.attributes); + if (!url) { + throw new NotFoundException(ResponseMessages.issuance.error.agentUrlNotFound); + } - const csvFields: string[] = [TemplateIdentifier.EMAIL_COLUMN]; + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } + } - const flattendData = extractAttributeNames(attributesArray); - csvFields.push(...flattendData); + async downloadBulkIssuanceCSVTemplate(templateDetails: TemplateDetailsInterface): Promise { + try { + let schemaResponse: SchemaDetails; + let fileName: string; + const {schemaType, templateId} = templateDetails; + + if (!templateId) { + throw new BadRequestException(ResponseMessages.bulkIssuance.error.invalidtemplateId); + } + const timestamp = Math.floor(Date.now() / 1000); + + if (schemaType === SchemaType.INDY) { + schemaResponse = await this.issuanceRepository.getCredentialDefinitionDetails(templateId); + if (!schemaResponse) { + throw new NotFoundException(ResponseMessages.bulkIssuance.error.invalidIdentifier); + } + fileName = `${schemaResponse.tag}-${timestamp}.csv`; + + } else if (schemaType === SchemaType.W3C_Schema) { + const schemDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier(templateId); + const {attributes, schemaLedgerId, name} = schemDetails; + schemaResponse = { attributes, schemaLedgerId, name }; + if (!schemaResponse) { + throw new NotFoundException(ResponseMessages.bulkIssuance.error.invalidIdentifier); + } + fileName = `${schemaResponse.name}-${timestamp}.csv`; + } const jsonData = []; + const attributesArray = JSON.parse(schemaResponse.attributes); + + // Extract the 'attributeName' values from the objects and store them in an array + const attributeNameArray = attributesArray.map(attribute => attribute.attributeName); + attributeNameArray.unshift(TemplateIdentifier.EMAIL_COLUMN); + + const [csvData, csvFields] = [jsonData, attributeNameArray]; - if (!csvFields.length) { + if (!csvData || !csvFields) { // eslint-disable-next-line prefer-promise-reject-errors return Promise.reject('Unable to transform schema data for CSV.'); } - const csv = parse(jsonData, { fields: csvFields }); + const csv = parse(csvFields, { fields: csvFields }); + const filePath = join(process.cwd(), `uploadedFiles/exports`); + + await createFile(filePath, fileName, csv); const fullFilePath = join(process.cwd(), `uploadedFiles/exports/${fileName}`); this.logger.log('fullFilePath::::::::', fullFilePath); //remove after user if (!checkIfFileOrDirectoryExists(fullFilePath)) { throw new NotFoundException(ResponseMessages.bulkIssuance.error.PathNotFound); } + // https required to download csv from frontend side const filePathToDownload = `${process.env.API_GATEWAY_PROTOCOL_SECURE}://${process.env.UPLOAD_LOGO_HOST}/${fileName}`; return { @@ -1243,14 +1064,13 @@ export class IssuanceService { fileName }; } catch (error) { - this.logger.error(`error in downloading csv : ${JSON.stringify(error.response)}`); - throw new RpcException(error?.response ? error?.response : error); + throw new Error(ResponseMessages.bulkIssuance.error.exportFile); } } - async uploadCSVTemplate(importFileDetails: ImportFileDetails, orgId: string, requestId?: string): Promise { + + async uploadCSVTemplate(importFileDetails: ImportFileDetails, requestId?: string): Promise { try { - this.logger.debug('REached here in:: uploadCSVTemplate '); let credentialDetails; const credentialPayload: ICredentialPayload = { schemaLedgerId: '', @@ -1260,31 +1080,15 @@ export class IssuanceService { credentialType: '', schemaName: '' }; - - const orgDetails = await this.issuanceRepository.getAgentEndPoint(orgId); - const { fileName, templateId, type, isValidateSchema } = importFileDetails; + const { fileName, templateId, type } = importFileDetails; if (type === SchemaType.W3C_Schema) { credentialDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier(templateId); - if (orgDetails?.ledgerId !== credentialDetails?.ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); - } - credentialPayload.schemaLedgerId = credentialDetails.schemaLedgerId; credentialPayload.credentialDefinitionId = SchemaType.W3C_Schema; credentialPayload.credentialType = SchemaType.W3C_Schema; credentialPayload.schemaName = credentialDetails.name; } else if (type === SchemaType.INDY) { credentialDetails = await this.issuanceRepository.getCredentialDefinitionDetails(templateId); - const schemaDetails = await this.issuanceRepository.getSchemaDetails(credentialDetails.schemaLedgerId); - - if (!schemaDetails) { - throw new NotFoundException(ResponseMessages.schema.error.notFound); - } - - if (orgDetails?.ledgerId !== schemaDetails?.ledgerId) { - throw new BadRequestException(ResponseMessages.issuance.error.ledgerMismatched); - } - credentialPayload.schemaLedgerId = credentialDetails.schemaLedgerId; credentialPayload.credentialDefinitionId = credentialDetails.credentialDefinitionId; credentialPayload.credentialType = SchemaType.INDY; @@ -1292,6 +1096,7 @@ export class IssuanceService { } const getFileDetails = await this.awsService.getFile(importFileDetails.fileKey); + const csvData: string = getFileDetails.Body.toString(); const parsedData = paParse(csvData, { @@ -1301,8 +1106,6 @@ export class IssuanceService { complete: (results) => results.data }); - const nestedObject = parsedData.data.map((row) => unflattenCsvRow(row)); - if (0 >= parsedData.data.length) { throw new BadRequestException(ResponseMessages.bulkIssuance.error.emptyFile); } @@ -1310,13 +1113,13 @@ export class IssuanceService { if (0 >= parsedData.meta.fields.length) { throw new BadRequestException(ResponseMessages.bulkIssuance.error.emptyheader); } - const invalidEmails = parsedData.data.filter((entry) => !validateEmail(entry.email_identifier.trim())); + const invalidEmails = parsedData.data.filter((entry) => !validateEmail(entry.email_identifier)); if (0 < invalidEmails.length) { throw new BadRequestException(ResponseMessages.bulkIssuance.error.invalidEmails); } - - const fileData: string[][] = nestedObject.map(Object.values); + + const fileData: string[][] = parsedData.data.map(Object.values); const fileHeader: string[] = parsedData.meta.fields; const attributesArray = JSON.parse(credentialDetails.attributes); @@ -1328,11 +1131,11 @@ export class IssuanceService { let validatedData; - if (type === SchemaType.W3C_Schema && isValidateSchema) { + if (type === SchemaType.W3C_Schema) { validatedData = parsedData.data.map((row) => { const { email_identifier, ...rest } = row; const newRow = { ...rest }; - + attributesArray.forEach((attr) => { if (!(attr?.attributeName in newRow)) { throw new BadRequestException(`Missing attribute ${attr?.attributeName} in CSV data`); @@ -1346,57 +1149,42 @@ export class IssuanceService { newRow[attr?.attributeName] = String(newRow[attr?.attributeName]); } }); - - return { email_identifier, ...newRow }; - }); - } else if (type === SchemaType.W3C_Schema && !isValidateSchema) { - validatedData = nestedObject.map((row) => { - const { email_identifier, ...rest } = row; - const newRow = { ...rest }; - + return { email_identifier, ...newRow }; }); } - this.logger.debug(`validatedData::::${JSON.stringify(validatedData)}`); + const finalFileData = { data: validatedData, errors: [], meta: parsedData.meta }; - if (isValidateSchema) { - await this.validateFileHeaders(fileHeader, attributeNameArray); - await this.validateFileData(fileData, attributesArray, fileHeader); - } + await this.validateFileHeaders(fileHeader, attributeNameArray); + await this.validateFileData(fileData, attributesArray, fileHeader); credentialPayload.fileData = type === SchemaType.W3C_Schema ? finalFileData : parsedData; credentialPayload.fileName = fileName; - this.logger.debug(`credentialPayload:::${JSON.stringify(credentialPayload)}`); const newCacheKey = uuidv4(); - const cacheTTL = Number(process.env.FILEUPLOAD_CACHE_TTL) || CommonConstants.DEFAULT_CACHE_TTL; - const store = await redisStore({ - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT) - }); - await store.set(requestId || newCacheKey, JSON.stringify(credentialPayload), cacheTTL).catch((error) => { - this.logger.error(`Error in setting the cache${error}`); - throw new RpcException('Failed to set cache'); - }); - return newCacheKey; - } catch (error) { - this.logger.error(`error in validating credentials : ${error}`); + + await this.cacheManager.set(requestId ? requestId : newCacheKey, JSON.stringify(credentialPayload), 60000); + +return newCacheKey; + +} catch (error) { + this.logger.error(`error in validating credentials : ${error.response}`); throw new RpcException(error.response ? error.response : error); } } - async previewFileDataForIssuance(requestId: string, previewRequest: PreviewRequest): Promise { + + async previewFileDataForIssuance( + requestId: string, + previewRequest: PreviewRequest + ): Promise { try { if ('' !== requestId.trim()) { - const store = await redisStore({ - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT) - }); - const cachedData = await store.get(requestId); + const cachedData = await this.cacheManager.get(requestId); if (!cachedData) { throw new NotFoundException(ResponseMessages.issuance.error.emptyFileData); } @@ -1404,20 +1192,19 @@ export class IssuanceService { throw new BadRequestException(ResponseMessages.issuance.error.previewCachedData); } const parsedData = JSON.parse(cachedData as string).fileData.data; - + // Apply search to the entire dataset if searchByText is provided let filteredData = parsedData; if (previewRequest.searchByText) { const searchTerm = previewRequest.searchByText.toLowerCase(); - filteredData = parsedData.filter( - (item) => - item.email_identifier.toLowerCase().includes(searchTerm) || item.name.toLowerCase().includes(searchTerm) + filteredData = parsedData.filter(item => item.email_identifier.toLowerCase().includes(searchTerm) || + item.name.toLowerCase().includes(searchTerm) ); } - + // Apply pagination to the filtered data const finalData = paginator(filteredData, previewRequest.pageNumber, previewRequest.pageSize); - + return finalData; } else { throw new BadRequestException(ResponseMessages.issuance.error.previewFile); @@ -1427,10 +1214,14 @@ export class IssuanceService { throw new RpcException(error.response); } } + - async getFileDetailsByFileId(fileId: string, getAllfileDetails: PreviewRequest): Promise { + async getFileDetailsByFileId( + fileId: string, + getAllfileDetails: PreviewRequest + ): Promise { try { - this.logger.log('getFileDetailsByFileId: get file details by fileId'); + const fileData = await this.issuanceRepository.getFileDetailsByFileId(fileId, getAllfileDetails); const fileResponse = { @@ -1448,28 +1239,30 @@ export class IssuanceService { } else { throw new NotFoundException(ResponseMessages.issuance.error.fileNotFound); } + } catch (error) { this.logger.error(`error in issuedFileDetails : ${error}`); throw new RpcException(error.response); } } - async issuedFileDetails(orgId: string, getAllfileDetails: PreviewRequest): Promise { + async issuedFileDetails( + orgId: string, + getAllfileDetails: PreviewRequest + ): Promise { try { - this.logger.log('issuedFileDetails: Get issued file details'); + const fileDetails = await this.issuanceRepository.getAllFileDetails(orgId, getAllfileDetails); - const templateIds = fileDetails?.fileList.map((file) => file.templateId); + const templateIds = fileDetails?.fileList.map(file => file.templateId); const getSchemaDetails = await this._getSchemaDetails(templateIds); - const fileListWithSchema = fileDetails?.fileList.map((file) => { - const schemaDetail = getSchemaDetails?.find((schema) => schema.schemaLedgerId === file.templateId); + const fileListWithSchema = fileDetails?.fileList.map(file => { + const schemaDetail = getSchemaDetails?.find(schema => schema.schemaLedgerId === file.templateId); return { ...file, - schema: schemaDetail - ? { name: schemaDetail.name, version: schemaDetail.version, schemaType: schemaDetail.type } - : null + schema: schemaDetail ? { name: schemaDetail.name, version: schemaDetail.version, schemaType: schemaDetail.type } : null }; }); @@ -1488,6 +1281,7 @@ export class IssuanceService { } else { throw new NotFoundException(ResponseMessages.issuance.error.notFound); } + } catch (error) { this.logger.error(`error in issuedFileDetails : ${error}`); throw new RpcException(error.response); @@ -1500,9 +1294,9 @@ export class IssuanceService { const payload = { templateIds }; - const schemaDetails = await this.natsClient - .send(this.issuanceServiceProxy, pattern, payload) - + const schemaDetails = await this.issuanceServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1516,20 +1310,21 @@ export class IssuanceService { return schemaDetails; } - async delay(ms): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + + async delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); } /** * Processes bulk payload in batches and adds jobs to the queue. * @param bulkPayload - * @param clientDetails + * @param clientDetails * @param orgId * @param requestId */ - - private async processInBatches(bulkPayload, bulkPayloadDetails: BulkPayloadDetails): Promise { - const { clientId, isRetry, orgId, requestId, isValidateSchema } = bulkPayloadDetails; + + private async processInBatches(bulkPayload, bulkPayloadDetails: BulkPayloadDetails):Promise { + const {clientId, isRetry, orgId, requestId} = bulkPayloadDetails; const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); const batchSize = CommonConstants.ISSUANCE_BATCH_SIZE; // initial 1000 const uniqueJobId = uuidv4(); @@ -1561,22 +1356,20 @@ export class IssuanceService { credentialType: item.credential_type, totalJobs: bulkPayload.length, isRetry, - isValidateSchema, isLastData: false, organizationLogoUrl: bulkPayloadDetails?.organizationLogoUrl, platformName: bulkPayloadDetails?.platformName, certificate: bulkPayloadDetails?.certificate, size: bulkPayloadDetails?.size, - orientation: bulkPayloadDetails?.orientation, - height: bulkPayloadDetails?.height, - width: bulkPayloadDetails?.width + orientation: bulkPayloadDetails?.orientation } })); this.logger.log(`Processing batch ${batchIndex + 1} with ${batch.length} items.`); // Execute the batched jobs with limited concurrency - await Promise.all(queueJobsArray.map((job) => limit(() => job))); + await Promise.all(queueJobsArray.map(job => limit(() => job))); + return queueJobsArray; }; @@ -1585,13 +1378,13 @@ export class IssuanceService { for (const batch of createBatches(bulkPayload, batchSize)) { const resolvedBatchJobs = await processBatch(batch, batchIndex); - this.logger.log('Adding resolved jobs to the queue:', resolvedBatchJobs); + this.logger.log("Adding resolved jobs to the queue:", resolvedBatchJobs); await this.bulkIssuanceQueue.addBulk(resolvedBatchJobs); batchIndex++; // Wait for 60 seconds before processing the next batch, if more batches are remaining - if (batchIndex * batchSize < bulkPayload.length) { + if ((batchIndex * batchSize) < bulkPayload.length) { await delay(CommonConstants.ISSUANCE_BATCH_DELAY); } } @@ -1609,8 +1402,7 @@ export class IssuanceService { requestId: string, orgId: string, clientDetails: IClientDetails, - reqPayload: ImportFileDetails, - isValidateSchema: boolean + reqPayload: ImportFileDetails ): Promise { if (!requestId) { throw new BadRequestException(ResponseMessages.issuance.error.missingRequestId); @@ -1628,20 +1420,16 @@ export class IssuanceService { let csvFileDetail; try { - const store = await redisStore({ - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT) - }); - let cachedData = await store.get(requestId); + let cachedData = await this.cacheManager.get(requestId); if (!cachedData) { throw new BadRequestException(ResponseMessages.issuance.error.cacheTimeOut); } // For demo UI if (cachedData && clientDetails?.isSelectiveIssuance) { - await store.del(requestId); + await this.cacheManager.del(requestId); await this.uploadCSVTemplate(reqPayload, requestId); - cachedData = await store.get(requestId); + cachedData = await this.cacheManager.get(requestId); } const parsedData = JSON.parse(cachedData as string).fileData.data; @@ -1682,22 +1470,20 @@ export class IssuanceService { } try { + const bulkPayloadDetails: BulkPayloadDetails = { clientId: clientDetails.clientId, orgId, requestId, - isValidateSchema, isRetry: false, organizationLogoUrl: clientDetails?.organizationLogoUrl, platformName: clientDetails?.platformName, certificate: clientDetails?.certificate, size: clientDetails?.size, - orientation: clientDetails?.orientation, - height: clientDetails?.height, - width: clientDetails?.width + orientation: clientDetails?.orientation }; - this.processInBatches(bulkPayload, bulkPayloadDetails); + this.processInBatches(bulkPayload, bulkPayloadDetails); } catch (error) { this.logger.error(`Error processing issuance data: ${error}`); } @@ -1715,12 +1501,7 @@ export class IssuanceService { } } - async retryBulkCredential( - fileId: string, - orgId: string, - clientDetails: IClientDetails, - isValidateSchema?: boolean - ): Promise { + async retryBulkCredential(fileId: string, orgId: string, clientDetails: IClientDetails): Promise { let bulkpayloadRetry; try { const fileDetails = await this.issuanceRepository.getFileDetailsById(fileId); @@ -1732,34 +1513,32 @@ export class IssuanceService { const errorMessage = ResponseMessages.bulkIssuance.error.fileDetailsNotFound; throw new BadRequestException(`${errorMessage}`); } - + try { const bulkPayloadDetails: BulkPayloadDetails = { - clientId: clientDetails.clientId, + clientId : clientDetails.clientId, orgId, isRetry: true, - isValidateSchema, organizationLogoUrl: clientDetails?.organizationLogoUrl, platformName: clientDetails?.platformName, certificate: clientDetails?.certificate, size: clientDetails?.size, - orientation: clientDetails?.orientation, - height: clientDetails?.height, - width: clientDetails?.width + orientation: clientDetails?.orientation }; this.processInBatches(bulkpayloadRetry, bulkPayloadDetails); - } catch (error) { - this.logger.error(`Error processing issuance data: ${error}`); - } - + } catch (error) { + this.logger.error(`Error processing issuance data: ${error}`); + } + return ResponseMessages.bulkIssuance.success.reinitiated; } catch (error) { throw new RpcException(error.response ? error.response : error); } } + async processIssuanceData(jobDetails: IQueuePayload): Promise { - const { jobId, totalJobs } = jobDetails; + const {jobId, totalJobs} = jobDetails; if (!this.processedJobsCounters[jobId]) { this.processedJobsCounters[jobId] = 0; } @@ -1805,48 +1584,42 @@ export class IssuanceService { isReuseConnection: true }; for (const key in jobDetails?.credential_data) { + if (jobDetails.credential_data.hasOwnProperty(key) && TemplateIdentifier.EMAIL_COLUMN !== key) { const value = jobDetails?.credential_data[key]; oobIssuancepayload.attributes.push({ name: key, value }); } } } else if (jobDetails.credentialType === SchemaType.W3C_Schema) { - const schemaDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier( - jobDetails.schemaLedgerId - ); - const { name, schemaLedgerId } = schemaDetails; - const JsonldCredentialDetails: IJsonldCredential = { - schemaName: name, - schemaLedgerId, - credentialData: jobDetails.credential_data, - orgDid, - orgId, - isReuseConnection: true - }; + const schemaDetails = await this.issuanceRepository.getSchemaDetailsBySchemaIdentifier(jobDetails.schemaLedgerId); + const {name, schemaLedgerId} = schemaDetails; + const JsonldCredentialDetails: IJsonldCredential = { + schemaName : name, + schemaLedgerId, + credentialData: jobDetails.credential_data, + orgDid, + orgId, + isReuseConnection: true + }; - prettyVc = { - certificate: jobDetails?.certificate, - size: jobDetails?.size, - orientation: jobDetails?.orientation, - height: jobDetails?.height, - width: jobDetails?.width - }; + prettyVc = { + certificate: jobDetails?.certificate, + size: jobDetails?.size, + orientation: jobDetails?.orientation + }; - oobIssuancepayload = await createOobJsonldIssuancePayload(JsonldCredentialDetails, prettyVc); - oobIssuancepayload.isValidateSchema = jobDetails?.isValidateSchema; + oobIssuancepayload = await createOobJsonldIssuancePayload(JsonldCredentialDetails, prettyVc); } const oobCredentials = await this.outOfBandCredentialOffer( - oobIssuancepayload, - jobDetails?.platformName, - jobDetails?.organizationLogoUrl, - prettyVc - ); + oobIssuancepayload, jobDetails?.platformName, jobDetails?.organizationLogoUrl, prettyVc); if (oobCredentials) { await this.issuanceRepository.deleteFileDataByJobId(jobDetails.id); } } catch (error) { - this.logger.error(`error in issuanceBulkCredential for data : ${JSON.stringify(error)}`); + this.logger.error( + `error in issuanceBulkCredential for data : ${JSON.stringify(error)}` + ); fileUploadData.isError = true; fileUploadData.error = JSON.stringify(error.error) ? JSON.stringify(error.error) : JSON.stringify(error); fileUploadData.detailError = `${JSON.stringify(error)}`; @@ -1866,24 +1639,14 @@ export class IssuanceService { transports: ['websocket'] }); const errorCount = await this.issuanceRepository.countErrorsForFile(jobDetails.fileUploadId); - const status = 0 === errorCount ? FileUploadStatus.completed : FileUploadStatus.partially_completed; + const status = + 0 === errorCount ? FileUploadStatus.completed : FileUploadStatus.partially_completed; if (!jobDetails.isRetry) { - socket.emit('bulk-issuance-process-completed', { - clientId: jobDetails.clientId, - fileUploadId: jobDetails.fileUploadId - }); - - const store = await redisStore({ - host: process.env.REDIS_HOST, - port: Number(process.env.REDIS_PORT) - }); - store.del(jobDetails.cacheId); + socket.emit('bulk-issuance-process-completed', { clientId: jobDetails.clientId, fileUploadId: jobDetails.fileUploadId }); + this.cacheManager.del(jobDetails.cacheId); } else { - socket.emit('bulk-issuance-process-retry-completed', { - clientId: jobDetails.clientId, - fileUploadId: jobDetails.fileUploadId - }); + socket.emit('bulk-issuance-process-retry-completed', { clientId: jobDetails.clientId }); } await this.issuanceRepository.updateFileUploadDetails(jobDetails.fileUploadId, { @@ -1902,15 +1665,12 @@ export class IssuanceService { }); if (!isErrorOccurred) { isErrorOccurred = true; - socket.emit('error-in-bulk-issuance-process', { - clientId: jobDetails.clientId, - error, - fileUploadId: jobDetails.fileUploadId - }); + socket.emit('error-in-bulk-issuance-retry-process', { clientId: jobDetails.clientId, error }); } throw error; + } - return true; + return true; } async splitIntoBatches(array: T[], batchSize: number): Promise { @@ -1921,34 +1681,36 @@ export class IssuanceService { return batches; } - async validateFileHeaders(fileHeader: string[], schemaAttributes: string[]): Promise { + async validateFileHeaders( + fileHeader: string[], + schemaAttributes: string[] + ): Promise { try { const fileSchemaHeader: string[] = fileHeader.slice(); - if (TemplateIdentifier.EMAIL_COLUMN === fileHeader[0]) { - fileSchemaHeader.splice(0, 1); + if (TemplateIdentifier.EMAIL_COLUMN === fileHeader[0]) { + fileSchemaHeader.splice(0, 1); } else { - throw new BadRequestException(ResponseMessages.bulkIssuance.error.emailColumn); + throw new BadRequestException(ResponseMessages.bulkIssuance.error.emailColumn + ); } if (schemaAttributes.length !== fileSchemaHeader.length) { - throw new ConflictException(ResponseMessages.bulkIssuance.error.attributeNumber); + throw new ConflictException(ResponseMessages.bulkIssuance.error.attributeNumber + ); } - const mismatchedAttributes = fileSchemaHeader.filter((value) => !schemaAttributes.includes(value)); + const mismatchedAttributes = fileSchemaHeader.filter(value => !schemaAttributes.includes(value)); if (0 < mismatchedAttributes.length) { throw new ConflictException(ResponseMessages.bulkIssuance.error.mismatchedAttributes); } } catch (error) { throw error; + } } - async validateFileData( - fileData: string[][], - attributesArray: { attributeName: string; schemaDataType: string; displayName: string; isRequired: boolean }[], - fileHeader: string[] - ): Promise { + async validateFileData(fileData: string[][], attributesArray: { attributeName: string, schemaDataType: string, displayName: string, isRequired: boolean }[], fileHeader: string[]): Promise { try { const filedata = fileData.map((item: string[]) => { const fileHeaderData = item?.map((element, j) => ({ @@ -1962,6 +1724,7 @@ export class IssuanceService { filedata.forEach((attr, i) => { attr.forEach((eachElement) => { + attributesArray.forEach((eachItem) => { if (eachItem.attributeName === eachElement.header) { if (eachItem.isRequired && !eachElement.value) { @@ -1988,35 +1751,32 @@ export class IssuanceService { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.natsClient.send(this.issuanceServiceProxy, pattern, payload); + const message = await this.issuanceServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); } } async _storeBulkPayloadInBatch(bulkPayloadObject: IBulkPayloadObject): Promise { try { - const { parsedFileDetails, parsedData, fileUploadId, userId } = bulkPayloadObject; - + const {parsedFileDetails, parsedData, fileUploadId, userId} = bulkPayloadObject; + const limit = pLimit(CommonConstants.MAX_CONCURRENT_OPERATIONS); const startTime = Date.now(); const batches = await this.splitIntoBatches(parsedData, CommonConstants.BATCH_SIZE); - this.logger.log('Total number of batches:', batches.length); - + this.logger.log("Total number of batches:", batches.length); + for (const [index, batch] of batches.entries()) { - const batchStartTime = Date.now(); - + + const batchStartTime = Date.now(); + // Create an array of limited promises for the current batch - const saveFileDetailsPromises = batch.map((element) => - limit(() => { + const saveFileDetailsPromises = batch.map(element => limit(() => { const credentialPayload = { credential_data: element, schemaId: parsedFileDetails.schemaLedgerId, @@ -2030,46 +1790,44 @@ export class IssuanceService { return this.issuanceRepository.saveFileDetails(credentialPayload, userId); }) ); - + this.logger.log(`Processing batch ${index + 1} with ${batch.length} elements...`); - + // Wait for all operations in the current batch to complete before moving to the next batch await Promise.all(saveFileDetailsPromises); - + const batchEndTime = Date.now(); // End timing the current batch - this.logger.log(`Batch ${index + 1} processed in ${batchEndTime - batchStartTime} milliseconds.`); + this.logger.log(`Batch ${index + 1} processed in ${(batchEndTime - batchStartTime)} milliseconds.`); } - + const endTime = Date.now(); - this.logger.log(`Total processing time: ${endTime - startTime} milliseconds.`); + this.logger.log(`Total processing time: ${(endTime - startTime)} milliseconds.`); return true; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); } } async deleteIssuanceRecords(orgId: string, userDetails: user): Promise { try { - const getFileUploadData = await this.issuanceRepository.getFileUploadDataByOrgId(orgId); - const getFileUploadIds = getFileUploadData.map((fileData) => fileData.id); + const getFileUploadData = await this.issuanceRepository.getFileUploadDataByOrgId(orgId); + const getFileUploadIds = getFileUploadData.map(fileData => fileData.id); + await this.issuanceRepository.deleteFileUploadData(getFileUploadIds, orgId); const deletedCredentialsRecords = await this.issuanceRepository.deleteIssuanceRecordsByOrgId(orgId); - + if (0 === deletedCredentialsRecords?.deleteResult?.count) { throw new NotFoundException(ResponseMessages.issuance.error.issuanceRecordsNotFound); } - const statusCounts = { + const statusCounts = { [IssuanceProcessState.REQUEST_SENT]: 0, [IssuanceProcessState.REQUEST_RECEIVED]: 0, [IssuanceProcessState.PROPOSAL_SENT]: 0, @@ -2081,45 +1839,29 @@ export class IssuanceService { [IssuanceProcessState.CREDENTIAL_RECEIVED]: 0, [IssuanceProcessState.CREDENTIAL_ISSUED]: 0, [IssuanceProcessState.ABANDONED]: 0 - }; + }; - await Promise.all( - deletedCredentialsRecords?.recordsToDelete?.map(async (record) => { - statusCounts[record.state]++; - }) - ); + await Promise.all(deletedCredentialsRecords?.recordsToDelete?.map(async (record) => { + statusCounts[record.state]++; + })); - const filteredStatusCounts = Object.fromEntries(Object.entries(statusCounts).filter((entry) => 0 < entry[1])); + const filteredStatusCounts = Object.fromEntries( + Object.entries(statusCounts).filter(entry => 0 < entry[1]) + ); const deletedIssuanceData = { - deletedCredentialsRecordsCount: deletedCredentialsRecords?.deleteResult?.count, + deletedCredentialsRecordsCount : deletedCredentialsRecords?.deleteResult?.count, deletedRecordsStatusCount: filteredStatusCounts - }; - - await this.userActivityRepository._orgDeletedActivity( - orgId, - userDetails, - deletedIssuanceData, - RecordType.ISSUANCE_RECORD - ); + }; + await this.userActivityRepository._orgDeletedActivity(orgId, userDetails, deletedIssuanceData, RecordType.ISSUANCE_RECORD); + return deletedCredentialsRecords; } catch (error) { this.logger.error(`[deleteIssuanceRecords] - error in deleting issuance records: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async getFileDetailsAndFileDataByFileId(fileId: string, orgId: string): Promise { - try { - const fileDetails = await this.issuanceRepository.getFileDetailsAndFileDataByFileId(fileId, orgId); - if (!fileDetails) { - throw new NotFoundException(ResponseMessages.issuance.error.fileNotFound); - } - return fileDetails; - } catch (error) { - this.logger.error(`error in getFileDetailsAndFileDataByFileId : ${error}`); - throw new RpcException(error.response); - } - } + } diff --git a/apps/issuance/src/main.ts b/apps/issuance/src/main.ts index 5f435d931..cacad1b16 100644 --- a/apps/issuance/src/main.ts +++ b/apps/issuance/src/main.ts @@ -5,7 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { IssuanceModule } from '../src/issuance.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -16,7 +15,7 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.ISSUANCE_SERVICE, process.env.ISSUANCE_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/issuance/templates/out-of-band-issuance.template.ts b/apps/issuance/templates/out-of-band-issuance.template.ts index 9723ba305..d8956fbb8 100644 --- a/apps/issuance/templates/out-of-band-issuance.template.ts +++ b/apps/issuance/templates/out-of-band-issuance.template.ts @@ -56,7 +56,7 @@ export class OutOfBandIssuance { iOS App Store. (Skip, if already downloaded)
  • Complete the onboarding process in ${process.env.MOBILE_APP}.
  • -
  • Open the “Accept Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP})
  • +
  • Open the “Accept Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP} App)
  • Accept the Credential in ${process.env.MOBILE_APP}.
  • Check "Credentials" tab in ${process.env.MOBILE_APP} to view the issued credential.
  • diff --git a/apps/ledger/libs/helpers/w3c.schema.builder.ts b/apps/ledger/libs/helpers/w3c.schema.builder.ts deleted file mode 100644 index da967ff39..000000000 --- a/apps/ledger/libs/helpers/w3c.schema.builder.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { IW3CAttributeValue } from '@credebl/common/interfaces/interface'; -import { ISchemaAttributesFormat } from 'apps/ledger/src/schema/interfaces/schema-payload.interface'; -import { IProductSchema } from 'apps/ledger/src/schema/interfaces/schema.interface'; -import ExclusiveMinimum from 'libs/validations/exclusiveMinimum'; -import MaxItems from 'libs/validations/maxItems'; -import MaxLength from 'libs/validations/maxLength'; -import Minimum from 'libs/validations/minimum'; -import MinItems from 'libs/validations/minItems'; -import MinLength from 'libs/validations/minLength'; -import MultipleOf from 'libs/validations/multipleOf'; -import Pattern from 'libs/validations/pattern'; -import UniqueItems from 'libs/validations/uniqueItems'; - -export function w3cSchemaBuilder(attributes: IW3CAttributeValue[], schemaName: string, description: string): object { - // Function to apply validations based on attribute properties - const applyValidations = (attribute, propertyObj): ISchemaAttributesFormat => { - const context = { ...propertyObj }; - - // Apply string validations - if ('string' === attribute.schemaDataType.toLowerCase()) { - if (attribute.minLength !== undefined) { - const validation = new MinLength(attribute.minLength); - validation.json(context); - } - - if (attribute.maxLength !== undefined) { - const validation = new MaxLength(attribute.maxLength); - validation.json(context); - } - - if (attribute.pattern !== undefined) { - const validation = new Pattern(attribute.pattern); - validation.json(context); - } - } - - // Apply number validations - if (['number', 'integer'].includes(attribute.schemaDataType.toLowerCase())) { - if (attribute.minimum !== undefined) { - const validation = new Minimum(attribute.minimum); - validation.json(context); - } - - if (attribute.exclusiveMinimum !== undefined) { - const validation = new ExclusiveMinimum(attribute.exclusiveMinimum); - validation.json(context); - } - - if (attribute.multipleOf !== undefined) { - const validation = new MultipleOf(attribute.multipleOf); - validation.json(context); - } - } - - // Apply array validations - if ('array' === attribute.schemaDataType.toLowerCase()) { - if (attribute.minItems !== undefined) { - const validation = new MinItems(attribute.minItems); - validation.json(context); - } - - if (attribute.maxItems !== undefined) { - const validation = new MaxItems(attribute.maxItems); - validation.json(context); - } - - if (attribute.uniqueItems !== undefined) { - const validation = new UniqueItems(attribute.uniqueItems); - validation.json(context); - } - } - - return context; - }; - - // Function to recursively process attributes - const processAttributes = (attrs: IW3CAttributeValue[]): IProductSchema => { - if (!Array.isArray(attrs)) { - return { properties: {}, required: [] }; - } - - const properties = {}; - const required = []; - - attrs.forEach((attribute) => { - const { attributeName, schemaDataType, isRequired, displayName, description } = attribute; - - // Add to required array if isRequired is true - if (isRequired) { - required.push(attributeName); - } - - // Create base property object with common fields - const baseProperty = { - type: schemaDataType.toLowerCase(), - title: displayName || attributeName, - description: description ? description : `${attributeName} field` - }; - - // Handle different attribute types - if (['string', 'number', 'boolean', 'integer'].includes(schemaDataType.toLowerCase())) { - // Apply validations to the base property - properties[attributeName] = applyValidations(attribute, baseProperty); - } else if ('datetime-local' === schemaDataType.toLowerCase()) { - properties[attributeName] = { - ...baseProperty, - type: 'string', - format: 'date-time' - }; - } else if ('array' === schemaDataType.toLowerCase() && attribute.items) { - // Process array items - const arrayItemProperties = {}; - const arrayItemRequired = []; - - if (Array.isArray(attribute.items)) { - // If items is an array, process each item - attribute.items.forEach((item) => { - if ('object' === item.schemaDataType.toLowerCase() && item.properties) { - // Process object properties - const nestedObjProperties = {}; - const nestedObjRequired = []; - - // Process properties object - Object.keys(item.properties).forEach((propKey) => { - const prop = item.properties[propKey]; - - if (prop.isRequired) { - nestedObjRequired.push(prop.attributeName); - } - - if ('array' === prop.schemaDataType.toLowerCase() && prop.items) { - // Handle nested array - const nestedArrayResult = processAttributes(prop.items); - - nestedObjProperties[prop.attributeName] = { - type: prop.schemaDataType.toLowerCase(), - title: prop.displayName || prop.attributeName, - description: `${prop.attributeName} field`, - items: { - type: 'object', - properties: nestedArrayResult.properties - } - }; - - if (0 < nestedArrayResult.required.length) { - nestedObjProperties[prop.attributeName].items.required = nestedArrayResult.required; - } - } else { - // Handle basic property - nestedObjProperties[prop.attributeName] = { - type: prop.schemaDataType.toLowerCase(), - title: prop.displayName || prop.attributeName, - description: `${prop.attributeName} field` - }; - - // Apply validations - nestedObjProperties[prop.attributeName] = applyValidations( - prop, - nestedObjProperties[prop.attributeName] - ); - } - }); - - // Add object to array item properties - arrayItemProperties[item.attributeName] = { - type: 'object', - title: item.displayName || item.attributeName, - description: `${item.attributeName} field`, - properties: nestedObjProperties - }; - - if (0 < nestedObjRequired.length) { - arrayItemProperties[item.attributeName].required = nestedObjRequired; - } - - if (item.isRequired) { - arrayItemRequired.push(item.attributeName); - } - } else { - // Handle basic array item - arrayItemProperties[item.attributeName] = { - type: item.schemaDataType.toLowerCase(), - title: item.displayName || item.attributeName, - description: `${item.attributeName} field` - }; - - // Apply validations - arrayItemProperties[item.attributeName] = applyValidations(item, arrayItemProperties[item.attributeName]); - - if (item.isRequired) { - arrayItemRequired.push(item.attributeName); - } - } - }); - } - - properties[attributeName] = { - ...baseProperty, - items: { - type: 'object', - properties: arrayItemProperties - } - }; - - // Apply array-specific validations - properties[attributeName] = applyValidations(attribute, properties[attributeName]); - - // Add required properties to the items schema if any - if (0 < arrayItemRequired.length) { - properties[attributeName].items.required = arrayItemRequired; - } - } else if ('object' === schemaDataType.toLowerCase() && attribute.properties) { - const nestedProperties = {}; - const nestedRequired = []; - - // Process each property in the object - Object.keys(attribute.properties).forEach((propKey) => { - const prop = attribute.properties[propKey]; - - // Add to nested required array if isRequired is true - if (prop.isRequired) { - nestedRequired.push(propKey); - } - - // Create base property for nested object - const nestedBaseProperty = { - type: prop.schemaDataType.toLowerCase(), - title: prop.displayName || prop.attributeName, - description: `${prop.attributeName} field` - }; - - if ('array' === prop.schemaDataType.toLowerCase() && prop.items) { - // Handle nested arrays - const result = processAttributes(prop.items); - - nestedProperties[prop.attributeName] = { - ...nestedBaseProperty, - type: 'array', - items: { - type: 'object', - properties: result.properties - } - }; - - // Apply array-specific validations - nestedProperties[prop.attributeName] = applyValidations(prop, nestedProperties[prop.attributeName]); - - // Add required properties to the items schema if any - if (0 < result.required.length) { - nestedProperties[prop.attributeName].items.required = result.required; - } - } else { - // Handle basic properties with validations - nestedProperties[prop.attributeName] = applyValidations(prop, nestedBaseProperty); - } - }); - - properties[attributeName] = { - ...baseProperty, - type: 'object', - properties: nestedProperties - }; - - // Add required properties to the object schema if any - if (0 < nestedRequired.length) { - properties[attributeName].required = nestedRequired; - } - } - }); - - return { properties, required }; - }; - - // Process all attributes - const result = processAttributes(attributes); - const { properties } = result; - // Add id property to required fields along with other required fields - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const required = ['id', ...result.required]; - - // Add id property - properties['id'] = { - type: 'string', - format: 'uri' - }; - - // Create the final W3C Schema - const W3CSchema = { - $schema: 'https://json-schema.org/draft/2020-12/schema', - $id: `https://example.com/schemas/${schemaName.toLowerCase().replace(/\s+/g, '-')}`, - title: schemaName, - description, - type: 'object', - required: ['@context', 'issuer', 'issuanceDate', 'type', 'credentialSubject'], - properties: { - '@context': { - $ref: '#/$defs/context' - }, - type: { - type: 'array', - items: { - anyOf: [ - { - const: 'VerifiableCredential' - }, - { - const: schemaName - } - ] - } - }, - credentialSubject: { - $ref: '#/$defs/credentialSubject' - }, - id: { - type: 'string', - format: 'uri' - }, - issuer: { - $ref: '#/$defs/uriOrId' - }, - issuanceDate: { - type: 'string', - format: 'date-time' - }, - expirationDate: { - type: 'string', - format: 'date-time' - }, - credentialStatus: { - $ref: '#/$defs/credentialStatus' - }, - credentialSchema: { - $ref: '#/$defs/credentialSchema' - } - }, - $defs: { - context: { - type: 'array', - prefixItems: [ - { - const: 'https://www.w3.org/2018/credentials/v1' - } - ], - items: { - oneOf: [ - { - type: 'string', - format: 'uri' - }, - { - type: 'object' - }, - { - type: 'array', - items: false - } - ] - }, - minItems: 1, - uniqueItems: true - }, - credentialSubject: { - type: 'object', - required: ['id', ...result.required], - additionalProperties: false, - properties - }, - credentialSchema: { - oneOf: [ - { - $ref: '#/$defs/idAndType' - }, - { - type: 'array', - items: { - $ref: '#/$defs/idAndType' - }, - minItems: 1, - uniqueItems: true - } - ] - }, - credentialStatus: { - oneOf: [ - { - $ref: '#/$defs/idAndType' - }, - { - type: 'array', - items: { - $ref: '#/$defs/idAndType' - }, - minItems: 1, - uniqueItems: true - } - ] - }, - idAndType: { - type: 'object', - required: ['id', 'type'], - properties: { - id: { - type: 'string', - format: 'uri' - }, - type: { - type: 'string' - } - } - }, - uriOrId: { - oneOf: [ - { - type: 'string', - format: 'uri' - }, - { - type: 'object', - required: ['id'], - properties: { - id: { - type: 'string', - format: 'uri' - } - } - } - ] - } - } - }; - - return W3CSchema; -} diff --git a/apps/ledger/src/credential-definition/credential-definition.controller.ts b/apps/ledger/src/credential-definition/credential-definition.controller.ts index 138929978..a23660919 100644 --- a/apps/ledger/src/credential-definition/credential-definition.controller.ts +++ b/apps/ledger/src/credential-definition/credential-definition.controller.ts @@ -4,7 +4,7 @@ import { Controller, Logger } from '@nestjs/common'; import { CredentialDefinitionService } from './credential-definition.service'; import { MessagePattern } from '@nestjs/microservices'; -import { GetAllCredDefsPayload, GetCredDefBySchemaId, IPlatformCredDefs, SaveCredDefPayload } from './interfaces/create-credential-definition.interface'; +import { GetAllCredDefsPayload, GetCredDefBySchemaId, IPlatformCredDefs } from './interfaces/create-credential-definition.interface'; import { CreateCredDefPayload, GetCredDefPayload } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; import { CredDefSchema } from './interfaces/credential-definition.interface'; @@ -45,9 +45,4 @@ export class CredentialDefinitionController { const {orgId, schemaType} = payload; return this.credDefService.getAllCredentialTemplates(orgId, schemaType); } - - @MessagePattern({ cmd: 'store-cred-def-record' }) - async getSchemaRecordBySchemaId(payload: SaveCredDefPayload): Promise { - return this.credDefService.storeCredDefRecord(payload.credDefDetails); - } } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/credential-definition.module.ts b/apps/ledger/src/credential-definition/credential-definition.module.ts index a51ae4109..d01b67179 100644 --- a/apps/ledger/src/credential-definition/credential-definition.module.ts +++ b/apps/ledger/src/credential-definition/credential-definition.module.ts @@ -10,7 +10,6 @@ import { PrismaService } from '@credebl/prisma-service'; import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ ClientsModule.register([ @@ -28,8 +27,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; CredentialDefinitionService, CredentialDefinitionRepository, Logger, - PrismaService, - NATSClient + PrismaService ], controllers: [CredentialDefinitionController] }) diff --git a/apps/ledger/src/credential-definition/credential-definition.service.ts b/apps/ledger/src/credential-definition/credential-definition.service.ts index 05d9ffcb8..66ad72979 100644 --- a/apps/ledger/src/credential-definition/credential-definition.service.ts +++ b/apps/ledger/src/credential-definition/credential-definition.service.ts @@ -1,413 +1,369 @@ /* eslint-disable camelcase */ -import { ConflictException, HttpException, HttpStatus, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { + ConflictException, + HttpException, + Inject, + Injectable, + NotFoundException +} from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { CredentialDefinitionRepository } from './repositories/credential-definition.repository'; -import { - CreateCredDefPayload, - CredDefPayload, - GetAllCredDefsPayload, - GetCredDefBySchemaId, - GetCredDefPayload, - IPlatformCredDefs, - ISaveCredDef -} from './interfaces/create-credential-definition.interface'; +import { CreateCredDefPayload, CredDefPayload, GetAllCredDefsPayload, GetCredDefBySchemaId, GetCredDefPayload, IPlatformCredDefs } from './interfaces/create-credential-definition.interface'; import { credential_definition } from '@prisma/client'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { - CreateCredDefAgentRedirection, - CredDefSchema, - GetCredDefAgentRedirection -} from './interfaces/credential-definition.interface'; +import { CreateCredDefAgentRedirection, CredDefSchema, GetCredDefAgentRedirection } from './interfaces/credential-definition.interface'; import { map } from 'rxjs/operators'; import { OrgAgentType, SchemaType, SortValue } from '@credebl/enum/enum'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { ICredDefDetails, IPlatformCredDefsData } from '@credebl/common/interfaces/cred-def.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { from } from 'rxjs'; -import { ISchemaDetail } from '@credebl/common/interfaces/schema.interface'; @Injectable() export class CredentialDefinitionService extends BaseService { - constructor( - private readonly credentialDefinitionRepository: CredentialDefinitionRepository, - @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy, - // TODO: Remove duplicate, unused variable - @Inject(CACHE_MANAGER) private readonly cacheService: Cache, - private readonly natsClient: NATSClient - ) { - super('CredentialDefinitionService'); - } - - async createCredentialDefinition(payload: CreateCredDefPayload): Promise { - try { - const { credDef, user } = payload; - const { agentEndPoint, orgDid } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(credDef.orgId); - // eslint-disable-next-line yoda - const did = credDef.orgDid?.split(':').length >= 4 ? credDef.orgDid : orgDid; - const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(credDef.orgId); - - const userId = user.id; - credDef.tag = credDef.tag.trim(); - const dbResult: credential_definition = await this.credentialDefinitionRepository.getByAttribute( - credDef.schemaLedgerId, - credDef.tag - ); - - if (dbResult) { - throw new ConflictException(ResponseMessages.credentialDefinition.error.Conflict); - } - let credDefResponseFromAgentService; - - const orgAgentType = await this.credentialDefinitionRepository.getOrgAgentType( - getAgentDetails.org_agents[0].orgAgentTypeId - ); - if (OrgAgentType.DEDICATED === orgAgentType) { - const CredDefPayload = { - tag: credDef.tag, - schemaId: credDef.schemaLedgerId, - issuerId: did, - agentEndPoint, - orgId: credDef.orgId, - agentType: OrgAgentType.DEDICATED - }; + constructor( + private readonly credentialDefinitionRepository: CredentialDefinitionRepository, + @Inject('NATS_CLIENT') private readonly credDefServiceProxy: ClientProxy, + @Inject(CACHE_MANAGER) private cacheService: Cache - credDefResponseFromAgentService = await this._createCredentialDefinition(CredDefPayload); - } else if (OrgAgentType.SHARED === orgAgentType) { - const { tenantId } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(credDef.orgId); - - const CredDefPayload = { - tenantId, - method: 'registerCredentialDefinition', - payload: { - tag: credDef.tag, - schemaId: credDef.schemaLedgerId, - issuerId: did - }, - agentEndPoint, - orgId: credDef.orgId, - agentType: OrgAgentType.SHARED - }; - credDefResponseFromAgentService = await this._createCredentialDefinition(CredDefPayload); - } - const response = JSON.parse(JSON.stringify(credDefResponseFromAgentService.response)); - const schemaDetails = await this.credentialDefinitionRepository.getSchemaById(credDef.schemaLedgerId); - if (!schemaDetails) { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.schemaIdNotFound); - } - const credDefData: CredDefPayload = { - tag: '', - schemaLedgerId: '', - issuerId: '', - revocable: credDef.revocable, - createdBy: `0`, - lastChangedBy: `0`, - orgId: '0', - schemaId: '0', - credentialDefinitionId: '' - }; - - if ('finished' === response.state) { - credDefData.tag = response.credentialDefinition.tag; - credDefData.schemaLedgerId = response.credentialDefinition.schemaId; - credDefData.issuerId = response.credentialDefinition.issuerId; - credDefData.credentialDefinitionId = response.credentialDefinitionId; - credDefData.orgId = credDef.orgId; - credDefData.revocable = credDef.revocable; - credDefData.schemaId = schemaDetails.id; - credDefData.createdBy = userId; - credDefData.lastChangedBy = userId; - } else if ('finished' === response.credentialDefinition.state) { - credDefData.tag = response.credentialDefinition.credentialDefinition.tag; - credDefData.schemaLedgerId = response.credentialDefinition.credentialDefinition.schemaId; - credDefData.issuerId = response.credentialDefinition.credentialDefinition.issuerId; - credDefData.credentialDefinitionId = response.credentialDefinition.credentialDefinitionId; - credDefData.orgId = credDef.orgId; - credDefData.revocable = credDef.revocable; - credDefData.schemaId = schemaDetails.id; - credDefData.createdBy = userId; - credDefData.lastChangedBy = userId; - } - const credDefResponse = await this.credentialDefinitionRepository.saveCredentialDefinition(credDefData); - - delete credDefResponse.lastChangedBy; - delete credDefResponse.lastChangedDateTime; - - return credDefResponse; - } catch (error) { - this.logger.error(`Error in creating credential definition: ${JSON.stringify(error)}`); - if (error?.status?.message?.error) { - throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR - }); - } - throw new RpcException(error.response || error); + ) { + super('CredentialDefinitionService'); } - } - - async _createCredentialDefinition(payload: CreateCredDefAgentRedirection): Promise<{ - response: string; - }> { - try { - const pattern = { - cmd: 'agent-create-credential-definition' - }; - const credDefResponse = await from(this.natsClient.send(this.credDefServiceProxy, pattern, payload)) - .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { - this.logger.error(`Catch : ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, - error.statusCode ?? HttpStatus.INTERNAL_SERVER_ERROR - ); - }); - return credDefResponse; - } catch (error) { - this.logger.error(`Error in creating credential definition : ${JSON.stringify(error)}`); - throw error; + async createCredentialDefinition(payload: CreateCredDefPayload): Promise { + try { + const { credDef, user } = payload; + const { agentEndPoint, orgDid } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(credDef.orgId); + // eslint-disable-next-line yoda + const did = credDef.orgDid?.split(':').length >= 4 ? credDef.orgDid : orgDid; + const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(credDef.orgId); + + const userId = user.id; + credDef.tag = credDef.tag.trim(); + const dbResult: credential_definition = await this.credentialDefinitionRepository.getByAttribute( + credDef.schemaLedgerId, + credDef.tag + ); + + if (dbResult) { + throw new ConflictException(ResponseMessages.credentialDefinition.error.Conflict); + } + let credDefResponseFromAgentService; + + const orgAgentType = await this.credentialDefinitionRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); + if (OrgAgentType.DEDICATED === orgAgentType) { + const CredDefPayload = { + tag: credDef.tag, + schemaId: credDef.schemaLedgerId, + issuerId: did, + agentEndPoint, + orgId: credDef.orgId, + agentType: OrgAgentType.DEDICATED + }; + + credDefResponseFromAgentService = await this._createCredentialDefinition(CredDefPayload); + + } else if (OrgAgentType.SHARED === orgAgentType) { + const { tenantId } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(credDef.orgId); + + const CredDefPayload = { + tenantId, + method: 'registerCredentialDefinition', + payload: { + tag: credDef.tag, + schemaId: credDef.schemaLedgerId, + issuerId: did + }, + agentEndPoint, + orgId: credDef.orgId, + agentType: OrgAgentType.SHARED + }; + credDefResponseFromAgentService = await this._createCredentialDefinition(CredDefPayload); + } + const response = JSON.parse(JSON.stringify(credDefResponseFromAgentService.response)); + const schemaDetails = await this.credentialDefinitionRepository.getSchemaById(credDef.schemaLedgerId); + if (!schemaDetails) { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.schemaIdNotFound); + } + const credDefData: CredDefPayload = { + tag: '', + schemaLedgerId: '', + issuerId: '', + revocable: credDef.revocable, + createdBy: `0`, + lastChangedBy: `0`, + orgId: '0', + schemaId: '0', + credentialDefinitionId: '' + }; + + if ('finished' === response.state) { + credDefData.tag = response.credentialDefinition.tag; + credDefData.schemaLedgerId = response.credentialDefinition.schemaId; + credDefData.issuerId = response.credentialDefinition.issuerId; + credDefData.credentialDefinitionId = response.credentialDefinitionId; + credDefData.orgId = credDef.orgId; + credDefData.revocable = credDef.revocable; + credDefData.schemaId = schemaDetails.id; + credDefData.createdBy = userId; + credDefData.lastChangedBy = userId; + } else if ('finished' === response.credentialDefinition.state) { + credDefData.tag = response.credentialDefinition.credentialDefinition.tag; + credDefData.schemaLedgerId = response.credentialDefinition.credentialDefinition.schemaId; + credDefData.issuerId = response.credentialDefinition.credentialDefinition.issuerId; + credDefData.credentialDefinitionId = response.credentialDefinition.credentialDefinitionId; + credDefData.orgId = credDef.orgId; + credDefData.revocable = credDef.revocable; + credDefData.schemaId = schemaDetails.id; + credDefData.createdBy = userId; + credDefData.lastChangedBy = userId; + } + const credDefResponse = await this.credentialDefinitionRepository.saveCredentialDefinition(credDefData); + + delete credDefResponse.lastChangedBy; + delete credDefResponse.lastChangedDateTime; + + return credDefResponse; + + } catch (error) { + this.logger.error( + `Error in creating credential definition: ${JSON.stringify(error)}` + ); + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { + throw new RpcException({ + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, + statusCode: error?.status?.code + }); + + } else { + throw new RpcException(error.response ? error.response : error); + } + } } - } - async getAllPlatformCredDefs(credDefsPayload: IPlatformCredDefs): Promise { - try { - const { pageSize, pageNumber } = credDefsPayload; - const response = await this.credentialDefinitionRepository.getAllPlatformCredDefsDetails(credDefsPayload); - - const credDefResponse: IPlatformCredDefsData = { - totalItems: response.credDefCount, - hasNextPage: pageSize * pageNumber < response.credDefCount, - hasPreviousPage: 1 < pageNumber, - nextPage: pageNumber + 1, - previousPage: pageNumber - 1, - lastPage: Math.ceil(response.credDefCount / pageSize), - data: response.credDefResult - }; - - if (0 !== response.credDefCount) { - return credDefResponse; - } else { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); - } - } catch (error) { - this.logger.error(`Error in retrieving all credential definitions: ${error}`); - throw new RpcException(error.response ? error.response : error); + async _createCredentialDefinition(payload: CreateCredDefAgentRedirection): Promise<{ + response: string; + }> { + try { + const pattern = { + cmd: 'agent-create-credential-definition' + }; + + const credDefResponse = await this.credDefServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { + this.logger.error(`Catch : ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.statusCode, + error: error.message + }, error.error); + }); + return credDefResponse; + } catch (error) { + this.logger.error(`Error in creating credential definition : ${JSON.stringify(error)}`); + throw error; + } } - } - - async getCredentialDefinitionById(payload: GetCredDefPayload): Promise { - try { - const { credentialDefinitionId, orgId } = payload; - const { agentEndPoint } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(String(orgId)); - const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(String(orgId)); - const orgAgentType = await this.credentialDefinitionRepository.getOrgAgentType( - getAgentDetails.org_agents[0].orgAgentTypeId - ); - let credDefResponse; - if (OrgAgentType.DEDICATED === orgAgentType) { - const getSchemaPayload = { - credentialDefinitionId, - orgId, - agentEndPoint, - agentType: OrgAgentType.DEDICATED - }; - credDefResponse = await this._getCredentialDefinitionById(getSchemaPayload); - } else if (OrgAgentType.SHARED === orgAgentType) { - const { tenantId } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(String(orgId)); - const getSchemaPayload = { - orgId, - tenantId, - method: 'getCredentialDefinitionById', - payload: { credentialDefinitionId }, - agentType: OrgAgentType.SHARED, - agentEndPoint - }; - credDefResponse = await this._getCredentialDefinitionById(getSchemaPayload); + async getAllPlatformCredDefs(credDefsPayload: IPlatformCredDefs): Promise { + try { + const { pageSize, pageNumber } = credDefsPayload; + const response = await this.credentialDefinitionRepository.getAllPlatformCredDefsDetails(credDefsPayload); + + const credDefResponse: IPlatformCredDefsData = { + totalItems: response.credDefCount, + hasNextPage: pageSize * pageNumber < response.credDefCount, + hasPreviousPage: 1 < pageNumber, + nextPage: pageNumber + 1, + previousPage: pageNumber - 1, + lastPage: Math.ceil(response.credDefCount / pageSize), + data: response.credDefResult + }; + + if (0 !== response.credDefCount) { + return credDefResponse; + } else { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); + } + + + } catch (error) { + this.logger.error(`Error in retrieving all credential definitions: ${error}`); + throw new RpcException(error.response ? error.response : error); + } } - if (credDefResponse.response.resolutionMetadata.error) { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.credDefIdNotFound); - } - - return credDefResponse; - } catch (error) { - this.logger.error(`Error retrieving credential definition with id ${payload.credentialDefinitionId}`); - if (error?.status?.message?.error) { - throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR - }); - } - throw new RpcException(error.response || error); + + async getCredentialDefinitionById(payload: GetCredDefPayload): Promise { + try { + const { credentialDefinitionId, orgId } = payload; + const { agentEndPoint } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(String(orgId)); + const getAgentDetails = await this.credentialDefinitionRepository.getAgentType(String(orgId)); + const orgAgentType = await this.credentialDefinitionRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); + + let credDefResponse; + if (OrgAgentType.DEDICATED === orgAgentType) { + const getSchemaPayload = { + credentialDefinitionId, + orgId, + agentEndPoint, + agentType: OrgAgentType.DEDICATED + }; + credDefResponse = await this._getCredentialDefinitionById(getSchemaPayload); + } else if (OrgAgentType.SHARED === orgAgentType) { + const { tenantId } = await this.credentialDefinitionRepository.getAgentDetailsByOrgId(String(orgId)); + const getSchemaPayload = { + orgId, + tenantId, + method: 'getCredentialDefinitionById', + payload: { credentialDefinitionId }, + agentType: OrgAgentType.SHARED, + agentEndPoint + }; + credDefResponse = await this._getCredentialDefinitionById(getSchemaPayload); + } + if (credDefResponse.response.resolutionMetadata.error) { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.credDefIdNotFound); + } + + return credDefResponse; + } catch (error) { + this.logger.error(`Error retrieving credential definition with id ${payload.credentialDefinitionId}`); + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { + throw new RpcException({ + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, + statusCode: error?.status?.code + }); + + } else { + throw new RpcException(error.response ? error.response : error); + } + } } - } - async _getCredentialDefinitionById(payload: GetCredDefAgentRedirection): Promise<{ - response: string; - }> { - try { - const pattern = { - cmd: 'agent-get-credential-definition' - }; - const credDefResponse = await from(this.natsClient.send(this.credDefServiceProxy, pattern, payload)) - .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { - this.logger.error(`Catch : ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.message - }, - error.statusCode ?? HttpStatus.INTERNAL_SERVER_ERROR - ); - }); - return credDefResponse; - } catch (error) { - this.logger.error(`Error in creating credential definition : ${JSON.stringify(error)}`); - throw error; + async _getCredentialDefinitionById(payload: GetCredDefAgentRedirection): Promise<{ + response: string; + }> { + + try { + const pattern = { + cmd: 'agent-get-credential-definition' + }; + const credDefResponse = await this.credDefServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { + this.logger.error(`Catch : ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.statusCode, + error: error.message + }, error.error); + }); + return credDefResponse; + } catch (error) { + this.logger.error(`Error in creating credential definition : ${JSON.stringify(error)}`); + throw error; + } } - } - - async getAllCredDefs(payload: GetAllCredDefsPayload): Promise { - try { - const { credDefSearchCriteria, orgId } = payload; - const response = await this.credentialDefinitionRepository.getAllCredDefs(credDefSearchCriteria, orgId); - - const schemaIds = response?.map((item) => item?.schemaLedgerId); - const schemaDetails = await this._getSchemaDetails(schemaIds); - - const archivedSchemaIds = schemaDetails - .filter((schema) => schema.isSchemaArchived) - .map((schema) => schema.schemaLedgerId); - - const filteredResponse = response.filter((credDef) => !archivedSchemaIds.includes(credDef.schemaLedgerId)); - - const credDefResponse = { - totalItems: filteredResponse.length, - hasNextPage: credDefSearchCriteria.pageSize * credDefSearchCriteria.pageNumber < response.length, - hasPreviousPage: 1 < credDefSearchCriteria.pageNumber, - nextPage: credDefSearchCriteria.pageNumber + 1, - previousPage: credDefSearchCriteria.pageNumber - 1, - lastPage: Math.ceil(response.length / credDefSearchCriteria.pageSize), - data: filteredResponse - }; - - if (0 === filteredResponse.length) { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); - } - - return credDefResponse; - } catch (error) { - this.logger.error(`Error in retrieving credential definitions: ${error}`); - throw new RpcException(error.response ? error.response : error); + async getAllCredDefs(payload: GetAllCredDefsPayload): Promise { + try { + const { credDefSearchCriteria, orgId } = payload; + const response = await this.credentialDefinitionRepository.getAllCredDefs(credDefSearchCriteria, orgId); + const credDefResponse = { + totalItems: response.length, + hasNextPage: credDefSearchCriteria.pageSize * credDefSearchCriteria.pageNumber < response.length, + hasPreviousPage: 1 < credDefSearchCriteria.pageNumber, + nextPage: credDefSearchCriteria.pageNumber + 1, + previousPage: credDefSearchCriteria.pageNumber - 1, + lastPage: Math.ceil(response.length / credDefSearchCriteria.pageSize), + data: response + }; + + if (0 == response.length) { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); + } + return credDefResponse; + + } catch (error) { + this.logger.error(`Error in retrieving credential definitions: ${error}`); + throw new RpcException(error.response ? error.response : error); + } } - } - - async _getSchemaDetails(schemaIds: string[]): Promise { - const pattern = { cmd: 'get-schemas-details' }; - - const payload = { - templateIds: schemaIds - }; - const getSchemaDetails = await this.credDefServiceProxy - .send(pattern, payload) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - return getSchemaDetails; - } - - async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { - try { - const { schemaId } = payload; - const credDefListBySchemaId = - await this.credentialDefinitionRepository.getCredentialDefinitionBySchemaId(schemaId); - return credDefListBySchemaId; - } catch (error) { - this.logger.error(`Error in retrieving credential definitions: ${error}`); - throw new RpcException(error.response ? error.response : error); + async getCredentialDefinitionBySchemaId(payload: GetCredDefBySchemaId): Promise { + try { + const { schemaId } = payload; + const credDefListBySchemaId = await this.credentialDefinitionRepository.getCredentialDefinitionBySchemaId(schemaId); + return credDefListBySchemaId; + } catch (error) { + this.logger.error(`Error in retrieving credential definitions: ${error}`); + throw new RpcException(error.response ? error.response : error); + } } - } - - async getAllCredentialTemplates(orgId: string, schemaType: string): Promise { - const schemaTypeEnum = schemaType as SchemaType; - if (!Object.values(SchemaType).includes(schemaTypeEnum)) { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.InvalidSchemaType); - } - try { - const payload = { - orgId, - sortValue: SortValue.ASC, - credDefSortBy: 'id' - }; - - if (schemaType === SchemaType.W3C_Schema) { - const schemaDetailList = await this.credentialDefinitionRepository.getAllSchemaByOrgIdAndType( - orgId, - schemaType - ); - const schemaResponse = await Promise.all( - schemaDetailList.map(async (schemaDetails) => ({ - schemaCredDefName: `${schemaDetails.name}-${schemaDetails.version}`, - schemaName: schemaDetails.name, - schemaVersion: schemaDetails.version, - schemaAttributes: schemaDetails.attributes, - type: SchemaType.W3C_Schema, - schemaIdentifier: schemaDetails.schemaLedgerId, - createDateTime: schemaDetails.createDateTime, - organizationName: schemaDetails?.organisation?.name, - userName: schemaDetails?.organisation?.userOrgRoles[0]?.user?.firstName - })) - ); - - return schemaResponse; - } - const credDefSchemaList = await this.credentialDefinitionRepository.getAllCredDefsByOrgIdForBulk(payload); - - if (!credDefSchemaList) { - throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); - } - const activeCredDefSchemaList = credDefSchemaList.filter((item) => !item?.['isSchemaArchived']); - - return activeCredDefSchemaList; - } catch (error) { - this.logger.error(`get Cred-Defs and schema List By OrgId for bulk operations: ${JSON.stringify(error)}`); - if (error?.status?.message?.error) { - const reason = error?.status?.message?.error?.reason ?? error?.status?.message?.error; - throw new RpcException({ - message: reason, - statusCode: error?.status?.code - }); - } else { - throw new RpcException(error.response ?? error); - } + async getAllCredentialTemplates(orgId: string, schemaType: string): Promise { + const schemaTypeEnum = schemaType as SchemaType; + if (!Object.values(SchemaType).includes(schemaTypeEnum)) { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.InvalidSchemaType); + } + try { + const payload = { + orgId, + sortValue: SortValue.ASC, + credDefSortBy: 'id' + }; + + if (schemaType === SchemaType.W3C_Schema) { + const schemaDetailList = await this.credentialDefinitionRepository.getAllSchemaByOrgIdAndType(orgId, schemaType); + const schemaResponse = await Promise.all(schemaDetailList.map(async (schemaDetails) => ({ + schemaCredDefName: `${schemaDetails.name}-${schemaDetails.version}`, + schemaName: schemaDetails.name, + schemaVersion: schemaDetails.version, + schemaAttributes: schemaDetails.attributes, + type: SchemaType.W3C_Schema, + schemaIdentifier: schemaDetails.schemaLedgerId, + createDateTime: schemaDetails.createDateTime, + organizationName: schemaDetails?.organisation?.name, + userName: schemaDetails?.organisation?.userOrgRoles[0]?.user?.firstName + }))); + + return schemaResponse; + } + const credDefSchemaList: CredDefSchema[] = + await this.credentialDefinitionRepository.getAllCredDefsByOrgIdForBulk( + payload + ); + if (!credDefSchemaList) { + throw new NotFoundException(ResponseMessages.credentialDefinition.error.NotFound); + } + return credDefSchemaList; + } catch (error) { + this.logger.error( + `get Cred-Defs and schema List By OrgId for bulk operations: ${JSON.stringify(error)}` + ); + if (error?.status?.message?.error) { + const reason = error?.status?.message?.error?.reason ?? error?.status?.message?.error; + throw new RpcException({ + message: reason, + statusCode: error?.status?.code + }); + } else { + throw new RpcException(error.response ?? error); + } + } } - } async _getOrgAgentApiKey(orgId: string): Promise { const pattern = { cmd: 'get-org-agent-api-key' }; @@ -415,27 +371,14 @@ export class CredentialDefinitionService extends BaseService { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.natsClient.send(this.credDefServiceProxy, pattern, payload); + const message = await this.credDefServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - } - } - - async storeCredDefRecord(credDefDetails: ISaveCredDef): Promise { - try { - const schemaSearchResult = await this.credentialDefinitionRepository.storeCredDefRecord(credDefDetails); - return schemaSearchResult; - } catch (error) { - this.logger.error(`Error in getSchemaBySchemaId: ${error}`); - throw new RpcException(error.response ? error.response : error); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); } } -} +} \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts index dbbd2c876..0e8a31cbd 100644 --- a/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts +++ b/apps/ledger/src/credential-definition/interfaces/create-credential-definition.interface.ts @@ -64,19 +64,4 @@ export interface GetAllCredDefsPayload { export interface GetCredDefBySchemaId { schemaId: string -} - -export interface ISaveCredDef { - schemaLedgerId: string; - tag: string; - credentialDefinitionId: string; - revocable: boolean; - createdBy: string; - lastChangedBy: string; - orgId: string; - schemaId: string; -} - -export interface SaveCredDefPayload { - credDefDetails: ISaveCredDef } \ No newline at end of file diff --git a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts index 6923b49ea..20e2bdf15 100644 --- a/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts +++ b/apps/ledger/src/credential-definition/repositories/credential-definition.repository.ts @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -import { CredDefPayload, GetAllCredDefsDto, IPlatformCredDefs, ISaveCredDef } from '../interfaces/create-credential-definition.interface'; +import { CredDefPayload, GetAllCredDefsDto, IPlatformCredDefs } from '../interfaces/create-credential-definition.interface'; import { PrismaService } from '@credebl/prisma-service'; import { credential_definition, org_agents, org_agents_type, organisation, schema } from '@prisma/client'; import { Injectable, Logger } from '@nestjs/common'; @@ -243,8 +243,7 @@ export class CredentialDefinitionRepository { version: true, schemaLedgerId: true, orgId: true, - attributes: true, - isSchemaArchived: true + attributes: true } }); @@ -255,13 +254,11 @@ export class CredentialDefinitionRepository { if (matchingSchema) { return { credentialDefinitionId: credDef.credentialDefinitionId, - credentialDefinition: credDef.tag, schemaCredDefName: `${matchingSchema.name}:${matchingSchema.version}-${credDef.tag}`, schemaName: matchingSchema.name, schemaVersion: matchingSchema.version, schemaAttributes: matchingSchema.attributes, - schemaLedgerId: matchingSchema.schemaLedgerId, - isSchemaArchived: matchingSchema.isSchemaArchived + credentialDefinition: credDef.tag }; } return null; @@ -280,7 +277,6 @@ export class CredentialDefinitionRepository { return await this.prisma.schema.findMany({ where: { orgId, - isSchemaArchived: false, type: schemaType }, select: { @@ -330,27 +326,5 @@ export class CredentialDefinitionRepository { } } - async storeCredDefRecord(credDefDetails: ISaveCredDef): Promise { - try { - const saveResult = await this.prisma.credential_definition.create({ - data: { - schemaLedgerId: credDefDetails.schemaLedgerId, - tag: credDefDetails.tag, - credentialDefinitionId: credDefDetails.credentialDefinitionId, - revocable: credDefDetails.revocable, - createdBy: credDefDetails.createdBy, - lastChangedBy: credDefDetails.lastChangedBy, - orgId: credDefDetails.orgId, - schemaId: credDefDetails.schemaId - } - }); - return saveResult; - } catch (error) { - this.logger.error( - `Error in saving credential-definition: ${error.message} ` - ); - throw error; - } - } } \ No newline at end of file diff --git a/apps/ledger/src/ledger.controller.ts b/apps/ledger/src/ledger.controller.ts index 250203dfb..36c4bf529 100644 --- a/apps/ledger/src/ledger.controller.ts +++ b/apps/ledger/src/ledger.controller.ts @@ -4,7 +4,6 @@ import { MessagePattern } from '@nestjs/microservices'; import { ledgers } from '@prisma/client'; import { LedgerDetails } from './interfaces/ledgers.interface'; import { INetworkUrl } from '@credebl/common/interfaces/schema.interface'; -import { ISchemasList } from './schema/interfaces/schema.interface'; @Controller() export class LedgerController { @@ -24,9 +23,4 @@ export class LedgerController { async getNetworkDetailsById(payload: {id: string}): Promise { return this.ledgerService.getLedgerDetailsById(payload.id); } - - @MessagePattern({ cmd: 'get-schema-details-for-ecosystem' }) - async schemaDetailsForEcosystem(payload: {schemaArray: string[], search: string, pageSize: number, pageNumber: number}): Promise { - return this.ledgerService.schemaDetailsForEcosystem(payload); - } } \ No newline at end of file diff --git a/apps/ledger/src/ledger.module.ts b/apps/ledger/src/ledger.module.ts index ed5daa043..770f578ea 100644 --- a/apps/ledger/src/ledger.module.ts +++ b/apps/ledger/src/ledger.module.ts @@ -8,15 +8,9 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; import { LedgerRepository } from './repositories/ledger.repository'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; @Module({ imports: [ - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, ClientsModule.register([ { name: 'NATS_CLIENT', diff --git a/apps/ledger/src/ledger.service.ts b/apps/ledger/src/ledger.service.ts index 170f6c3ac..519415809 100644 --- a/apps/ledger/src/ledger.service.ts +++ b/apps/ledger/src/ledger.service.ts @@ -2,12 +2,10 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { BaseService } from 'libs/service/base.service'; import { LedgerRepository } from './repositories/ledger.repository'; import { RpcException } from '@nestjs/microservices'; -// eslint-disable-next-line camelcase import { ledgers } from '@prisma/client'; import { ResponseMessages } from '@credebl/common/response-messages'; import { LedgerDetails } from './interfaces/ledgers.interface'; import { INetworkUrl } from '@credebl/common/interfaces/schema.interface'; -import { ISchemasList } from './schema/interfaces/schema.interface'; @Injectable() export class LedgerService extends BaseService { @@ -62,22 +60,4 @@ export class LedgerService extends BaseService { throw new RpcException(error.response ? error.response : error); } } - - - async schemaDetailsForEcosystem(data: {schemaArray: string[], search: string, pageSize: number, pageNumber: number}): Promise { - - try { - const getSchemaDetails = await this.ledgerRepository.handleGetSchemas(data); - - if (!getSchemaDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } - - return getSchemaDetails; - } catch (error) { - this.logger.error(`Error in getLedgerDetailsById: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - } \ No newline at end of file diff --git a/apps/ledger/src/main.ts b/apps/ledger/src/main.ts index fe38aaa35..80cd06e95 100644 --- a/apps/ledger/src/main.ts +++ b/apps/ledger/src/main.ts @@ -5,8 +5,6 @@ import { Logger } from '@nestjs/common'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; - async function bootstrap(): Promise { const app = await NestFactory.createMicroservice(LedgerModule, { @@ -14,7 +12,6 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.LEDGER_SERVICE, process.env.LEDGER_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/ledger/src/repositories/ledger.repository.ts b/apps/ledger/src/repositories/ledger.repository.ts index 176d365f5..5d2fd0510 100644 --- a/apps/ledger/src/repositories/ledger.repository.ts +++ b/apps/ledger/src/repositories/ledger.repository.ts @@ -1,10 +1,8 @@ import { PrismaService } from '@credebl/prisma-service'; import { Injectable, Logger } from '@nestjs/common'; -// eslint-disable-next-line camelcase import { ledgers } from '@prisma/client'; import { LedgerDetails } from '../interfaces/ledgers.interface'; import { INetworkUrl } from '@credebl/common/interfaces/schema.interface'; -import { ISchemasList, ISchemasResult } from '../schema/interfaces/schema.interface'; @Injectable() @@ -60,54 +58,4 @@ export class LedgerRepository { throw error; } } - - async handleGetSchemas(data: {schemaArray: string[], search: string, pageSize: number, pageNumber: number}): Promise { - try { - const { schemaArray, search, pageSize, pageNumber } = data; - - const schemasResult: ISchemasResult[] = await this.prisma.schema.findMany({ - where: { - schemaLedgerId: { - in: schemaArray - }, - OR: [ - { version: { contains: search, mode: 'insensitive' } }, - { name: { contains: search, mode: 'insensitive' } }, - { schemaLedgerId: { contains: search, mode: 'insensitive' } } - ] - }, - take: pageSize, - skip: (pageNumber - 1) * pageSize, - orderBy: { - createDateTime: 'desc' - } - }); - - // Get the total count of schemas that match the query - const schemasCount = await this.prisma.schema.count({ - where: { - schemaLedgerId: { - in: schemaArray - }, - OR: [ - { version: { contains: search, mode: 'insensitive' } }, - { name: { contains: search, mode: 'insensitive' } }, - { schemaLedgerId: { contains: search, mode: 'insensitive' } } - ] - } - }); - - // Return the schemas and the total count - return { - schemasCount, - schemasResult - }; - - } catch (error) { - this.logger.error(`Error handling 'get-schemas' request: ${JSON.stringify(error)}`); - throw error; - } - } - - } \ No newline at end of file diff --git a/apps/ledger/src/schema/enum/schema.enum.ts b/apps/ledger/src/schema/enum/schema.enum.ts index 1a307a646..247e7050b 100644 --- a/apps/ledger/src/schema/enum/schema.enum.ts +++ b/apps/ledger/src/schema/enum/schema.enum.ts @@ -11,5 +11,5 @@ export enum SortFields { } export enum W3CSchemaVersion { - W3C_SCHEMA_VERSION = 'draft/2020-12' + W3C_SCHEMA_VERSION = 'draft-07' } diff --git a/apps/ledger/src/schema/interfaces/schema-payload.interface.ts b/apps/ledger/src/schema/interfaces/schema-payload.interface.ts index db8d09192..4c6eb1d63 100644 --- a/apps/ledger/src/schema/interfaces/schema-payload.interface.ts +++ b/apps/ledger/src/schema/interfaces/schema-payload.interface.ts @@ -18,7 +18,6 @@ export interface ISchema { endorserWriteTxn?: string; orgDid?: string; type?: string; - alias?: string; } export interface IAttributeValue { @@ -88,21 +87,6 @@ export interface SchemaPayload { title: string, } - export interface ISchemaAttributesFormat extends W3CSchemaAttributes{ - order: number, - description: string; - exclusiveMinimum?: number; - multipleOf?: number; - pattern?: string; - minLength?: number; - maxLength?: number; - items?: object[] | string [] | number []; - properties?: object; - format?: string; - minItems?: number; - maxItems?: number; - uniqueItems?: boolean; - } export interface W3CSchemaPayload { schemaPayload: SchemaPayload, orgId: string, @@ -120,21 +104,3 @@ export interface IdAttribute extends W3CSchemaAttributes { order?: string } - -export interface ISaveSchema { - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - issuerId: string; - createdBy: string; - lastChangedBy: string; - publisherDid: string; - orgId: string; - ledgerId: string; - type?: string; -} - -export interface SaveSchemaPayload { - schemaDetails: ISaveSchema -} \ No newline at end of file diff --git a/apps/ledger/src/schema/interfaces/schema.interface.ts b/apps/ledger/src/schema/interfaces/schema.interface.ts index 7791d5e2d..a31023ff0 100644 --- a/apps/ledger/src/schema/interfaces/schema.interface.ts +++ b/apps/ledger/src/schema/interfaces/schema.interface.ts @@ -1,6 +1,5 @@ -import { JSONSchemaType, SchemaTypeEnum } from '@credebl/enum/enum'; +import { JSONSchemaType, SchemaTypeEnum, W3CSchemaDataType } from '@credebl/enum/enum'; import { UserRoleOrgPermsDto } from '../dtos/user-role-org-perms.dto'; -import { IW3CAttributeValue } from '@credebl/common/interfaces/interface'; export interface IUserRequestInterface { id: string; @@ -29,7 +28,8 @@ export interface ISelectedOrgInterface { export interface IOrganizationInterface { name: string; description: string; - org_agents: IOrgAgentInterface[]; + org_agents: IOrgAgentInterface[] + } export interface IOrgAgentInterface { @@ -43,9 +43,9 @@ export interface IOrgAgentInterface { } export interface AgentDetails { - orgDid: string; - agentEndPoint: string; - tenantId: string; + orgDid: string; + agentEndPoint: string; + tenantId: string } export interface ISchemaData { @@ -66,11 +66,11 @@ export interface ISchemasWithCount { schemasCount: number; schemasResult: ISchemaData[]; } - -export interface IProductSchema { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - properties: Record; - required: string[]; +interface IW3CAttributeValue { + attributeName: string; + schemaDataType: W3CSchemaDataType; + displayName: string; + isRequired: boolean; } interface IAttributeValue { @@ -84,7 +84,7 @@ export interface ICreateSchema { schemaVersion?: string; schemaName: string; attributes: IAttributeValue[]; - orgId?: string; + orgId?: string; orgDid?: string; } export interface ICreateW3CSchema { @@ -94,44 +94,12 @@ export interface ICreateW3CSchema { schemaType: JSONSchemaType; } export interface IGenericSchema { - alias: string; type: SchemaTypeEnum; schemaPayload: ICreateSchema | ICreateW3CSchema; } export interface IschemaPayload { - schemaDetails: IGenericSchema; - user: IUserRequestInterface; - orgId: string; -} -export interface ISchemasResult { - id: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - ledgerId: string; - type: string; -} - -export interface ISchemasList { - schemasCount: number; - schemasResult: ISchemasResult[]; -} - -export interface IUpdateSchema { - alias: string; - schemaLedgerId: string; - orgId?: string; -} - -export interface UpdateSchemaResponse { - count: number; -} + schemaDetails: IGenericSchema, + user: IUserRequestInterface, + orgId: string +} \ No newline at end of file diff --git a/apps/ledger/src/schema/repositories/schema.repository.ts b/apps/ledger/src/schema/repositories/schema.repository.ts index f21c0678d..40c83c21c 100644 --- a/apps/ledger/src/schema/repositories/schema.repository.ts +++ b/apps/ledger/src/schema/repositories/schema.repository.ts @@ -1,14 +1,12 @@ -import { AgentDetails, ISchemasWithCount, IUpdateSchema, UpdateSchemaResponse } from '../interfaces/schema.interface'; /* eslint-disable camelcase */ import { ConflictException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; -import { ICredDefWithCount, IPlatformSchemasWithOrg } from '@credebl/common/interfaces/schema.interface'; -import { ISaveSchema, ISchema, ISchemaExist, ISchemaSearchCriteria } from '../interfaces/schema-payload.interface'; -import { Prisma, ledgers, org_agents, org_agents_type, organisation, schema } from '@prisma/client'; -import { SchemaType, SortValue } from '@credebl/enum/enum'; - -import { ISchemaId } from '../schema.interface'; import { PrismaService } from '@credebl/prisma-service'; +import { ledgers, org_agents, org_agents_type, organisation, schema } from '@prisma/client'; +import { ISchema, ISchemaExist, ISchemaSearchCriteria } from '../interfaces/schema-payload.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { AgentDetails, ISchemasWithCount } from '../interfaces/schema.interface'; +import { SchemaType, SortValue } from '@credebl/enum/enum'; +import { ICredDefWithCount, IPlatformSchemas } from '@credebl/common/interfaces/schema.interface'; @Injectable() export class SchemaRepository { @@ -38,9 +36,7 @@ export class SchemaRepository { publisherDid: schemaResult.issuerId.split(':')[4] || schemaResult.issuerId, orgId: schemaResult.orgId, ledgerId: schemaResult.ledgerId, - type: schemaResult.type, - isSchemaArchived: false, - alias: schemaResult.alias + type: schemaResult.type } }); return saveResult; @@ -51,37 +47,11 @@ export class SchemaRepository { } } - // Todo: Need to remove this to make the above function 'saveSchema' generic that only saves schemas and not makes any changes - async saveSchemaRecord(schemaDetails: ISaveSchema): Promise { - try { - const saveResult = await this.prisma.schema.create({ - data: { - name: schemaDetails.name, - version: schemaDetails.version, - attributes: schemaDetails.attributes, - schemaLedgerId: schemaDetails.schemaLedgerId, - issuerId: schemaDetails.issuerId, - createdBy: schemaDetails.createdBy, - lastChangedBy: schemaDetails.lastChangedBy, - publisherDid: schemaDetails.publisherDid, - orgId: String(schemaDetails.orgId), - ledgerId: schemaDetails.ledgerId, - type: schemaDetails.type - } - }); - return saveResult; - } catch (error) { - this.logger.error(`Error in saveSchemaRecord: ${error}`); - throw error; - } - } - async schemaExists(schemaName: string, schemaVersion: string): Promise { try { return this.prisma.schema.findMany({ where: { type: SchemaType.INDY, - isSchemaArchived: false, name: { contains: schemaName, mode: 'insensitive' @@ -103,7 +73,6 @@ export class SchemaRepository { const schemasResult = await this.prisma.schema.findMany({ where: { organisation: { id: orgId }, - isSchemaArchived: false, OR: [ { name: { contains: payload.searchByText, mode: 'insensitive' } }, { version: { contains: payload.searchByText, mode: 'insensitive' } }, @@ -121,9 +90,8 @@ export class SchemaRepository { publisherDid: true, orgId: true, issuerId: true, - alias: true, organisation: { - select: { + select:{ name: true, userOrgRoles: { select: { @@ -160,19 +128,6 @@ export class SchemaRepository { } } - async getSchemasDetailsBySchemaName(schemaName: string, orgId: string): Promise { - const schemaDetails = await this.prisma.schema.findMany({ - where: { - orgId, - name: { contains: schemaName, mode: 'insensitive' } - }, - - select: { schemaLedgerId: true } - }); - - return schemaDetails; - } - async getSchemasDetailsBySchemaIds(templateIds: string[]): Promise { try { const schemasResult = await this.prisma.schema.findMany({ @@ -196,24 +151,6 @@ export class SchemaRepository { } } - async archiveSchemasByDid(did: string): Promise { - try { - const schemasResult = await this.prisma.schema.updateMany({ - where: { - issuerId: did - }, - data: { - isSchemaArchived: true - } - }); - - return schemasResult; - } catch (error) { - this.logger.error(`Error in archive schemas: ${error}`); - throw error; - } - } - async getAgentDetailsByOrgId(orgId: string): Promise { try { const schemasResult = await this.prisma.org_agents.findFirst({ @@ -298,29 +235,27 @@ export class SchemaRepository { } } - async getAllSchemaDetails(payload: ISchemaSearchCriteria): Promise { + async getAllSchemaDetails(payload: ISchemaSearchCriteria): Promise { try { const { ledgerId, schemaType, searchByText, sortField, sortBy, pageSize, pageNumber } = payload; let schemaResult; - /** - * This is made so because the default pageNumber is set to 1 in DTO, + /** + * This is made so because the default pageNumber is set to 1 in DTO, * If there is any 'searchByText' field, we ignore the pageNumbers and search * in all available records. - * + * * Because in that case pageNumber would be insignificant. */ if (searchByText) { schemaResult = await this.prisma.schema.findMany({ where: { ledgerId, - isSchemaArchived: false, type: schemaType, OR: [ { name: { contains: searchByText, mode: 'insensitive' } }, { version: { contains: searchByText, mode: 'insensitive' } }, { schemaLedgerId: { contains: searchByText, mode: 'insensitive' } }, - { issuerId: { contains: searchByText, mode: 'insensitive' } }, - { alias: { contains: searchByText, mode: 'insensitive' } } + { issuerId: { contains: searchByText, mode: 'insensitive' } } ] }, select: { @@ -329,18 +264,11 @@ export class SchemaRepository { version: true, attributes: true, schemaLedgerId: true, - isSchemaArchived: true, createdBy: true, publisherDid: true, - orgId: true, // This field can be null + orgId: true, // This field can be null issuerId: true, - type: true, - alias: true, - organisation: { - select: { - name: true - } - } + type: true }, orderBy: { [sortField]: SortValue.DESC === sortBy ? SortValue.DESC : SortValue.ASC @@ -354,7 +282,6 @@ export class SchemaRepository { schemaResult = await this.prisma.schema.findMany({ where: { ledgerId, - isSchemaArchived: false, type: schemaType }, select: { @@ -363,18 +290,11 @@ export class SchemaRepository { version: true, attributes: true, schemaLedgerId: true, - isSchemaArchived: true, createdBy: true, publisherDid: true, - orgId: true, // This field can be null + orgId: true, // This field can be null issuerId: true, - type: true, - alias: true, - organisation: { - select: { - name: true - } - } + type: true }, orderBy: { [sortField]: SortValue.DESC === sortBy ? SortValue.DESC : SortValue.ASC @@ -392,7 +312,7 @@ export class SchemaRepository { }); // Handle null orgId in the response - const schemasWithDefaultOrgId = schemaResult.map((schema) => ({ + const schemasWithDefaultOrgId = schemaResult.map(schema => ({ ...schema, orgId: schema.orgId || null // Replace null orgId with 'N/A' or any default value })); @@ -445,23 +365,21 @@ export class SchemaRepository { } } - async schemaExist(payload: ISchemaExist): Promise< - { - id: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - ledgerId: string; - }[] - > { + async schemaExist(payload: ISchemaExist): Promise<{ + id: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; + ledgerId: string; + }[]> { try { return this.prisma.schema.findMany({ where: { @@ -474,18 +392,4 @@ export class SchemaRepository { throw error; } } - - async updateSchema(schemaDetails: IUpdateSchema): Promise { - const { alias, schemaLedgerId, orgId } = schemaDetails; - - try { - return await this.prisma.schema.updateMany({ - where: orgId ? { schemaLedgerId, orgId } : { schemaLedgerId }, - data: { alias } - }); - } catch (error) { - this.logger.error(`Error in updating schema details: ${error}`); - throw error; - } - } -} +} \ No newline at end of file diff --git a/apps/ledger/src/schema/schema.controller.ts b/apps/ledger/src/schema/schema.controller.ts index 45d36462c..f2592fd18 100644 --- a/apps/ledger/src/schema/schema.controller.ts +++ b/apps/ledger/src/schema/schema.controller.ts @@ -5,10 +5,9 @@ import { ISchema, ISchemaCredDeffSearchInterface, ISchemaExist, - ISchemaSearchPayload, - SaveSchemaPayload + ISchemaSearchPayload } from './interfaces/schema-payload.interface'; -import { Prisma, schema } from '@prisma/client'; +import { schema } from '@prisma/client'; import { ICredDefWithPagination, ISchemaData, @@ -16,9 +15,6 @@ import { ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; import { IschemaPayload } from './interfaces/schema.interface'; -import { ISchemaId } from './schema.interface'; -import { UpdateSchemaDto } from 'apps/api-gateway/src/schema/dtos/update-schema-dto'; - @Controller('schema') export class SchemaController { @@ -36,12 +32,6 @@ export class SchemaController { return this.schemaService.getSchemaDetails(templateIds); } - @MessagePattern({ cmd: 'get-schemas-details-by-name' }) - async getSchemasDetailsBySchemaName(payload:{schemaName:string, orgId:string}): Promise { - const {orgId, schemaName} = payload; - return this.schemaService.getSchemaDetailsBySchemaName(schemaName, orgId); - } - @MessagePattern({ cmd: 'get-schema-by-id' }) async getSchemaById(payload: ISchema): Promise { const { schemaId, orgId } = payload; @@ -83,24 +73,4 @@ export class SchemaController { }[]> { return this.schemaService.schemaExist(payload); } - - @MessagePattern({ cmd: 'archive-schemas' }) - async archiveSchemas(payload: {did: string}): Promise { - return this.schemaService.archiveSchemas(payload.did); - } - - @MessagePattern({ cmd: 'store-schema-record' }) - async saveSchemaRecord(payload: SaveSchemaPayload): Promise { - return this.schemaService.storeSchemaDetails(payload.schemaDetails); - } - - @MessagePattern({ cmd: 'get-schema-record-by-schema-id' }) - async getSchemaRecordBySchemaId(payload: {schemaId: string}): Promise { - return this.schemaService.getSchemaBySchemaId(payload.schemaId); - } - -@MessagePattern({ cmd: 'update-schema' }) - updateSchema(payload:{schemaDetails:UpdateSchemaDto}): Promise { - return this.schemaService.updateSchema(payload.schemaDetails); - } } \ No newline at end of file diff --git a/apps/ledger/src/schema/schema.interface.ts b/apps/ledger/src/schema/schema.interface.ts index 92d0e55d6..506cdc47e 100644 --- a/apps/ledger/src/schema/schema.interface.ts +++ b/apps/ledger/src/schema/schema.interface.ts @@ -1,8 +1,5 @@ import { IUserRequestInterface } from './interfaces/schema.interface'; -export interface ISchemaId { - schemaLedgerId: string; -} export interface SchemaSearchCriteria { schemaLedgerId: string; credentialDefinitionId: string; diff --git a/apps/ledger/src/schema/schema.module.ts b/apps/ledger/src/schema/schema.module.ts index a8c8609d3..795783bef 100644 --- a/apps/ledger/src/schema/schema.module.ts +++ b/apps/ledger/src/schema/schema.module.ts @@ -10,7 +10,6 @@ import { PrismaService } from '@credebl/prisma-service'; import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ ClientsModule.register([ @@ -29,8 +28,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; SchemaService, SchemaRepository, Logger, - PrismaService, - NATSClient + PrismaService ], controllers: [SchemaController] }) diff --git a/apps/ledger/src/schema/schema.service.ts b/apps/ledger/src/schema/schema.service.ts index 276c840ed..ccaee62c6 100644 --- a/apps/ledger/src/schema/schema.service.ts +++ b/apps/ledger/src/schema/schema.service.ts @@ -5,47 +5,18 @@ import { Inject, ConflictException, Injectable, - NotAcceptableException, - NotFoundException, - HttpStatus -} from '@nestjs/common'; + NotAcceptableException, NotFoundException} from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { BaseService } from 'libs/service/base.service'; import { SchemaRepository } from './repositories/schema.repository'; -import { Prisma, schema } from '@prisma/client'; -import { - ISaveSchema, - ISchema, - ISchemaCredDeffSearchInterface, - ISchemaExist, - ISchemaSearchCriteria, - W3CCreateSchema -} from './interfaces/schema-payload.interface'; +import { schema } from '@prisma/client'; +import { ISchema, ISchemaCredDeffSearchInterface, ISchemaExist, ISchemaSearchCriteria, W3CCreateSchema } from './interfaces/schema-payload.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { - ICreateSchema, - ICreateW3CSchema, - IGenericSchema, - IUpdateSchema, - IUserRequestInterface, - UpdateSchemaResponse -} from './interfaces/schema.interface'; -import { CreateSchemaAgentRedirection, GetSchemaAgentRedirection, ISchemaId } from './schema.interface'; +import { ICreateSchema, ICreateW3CSchema, IGenericSchema, IUserRequestInterface } from './interfaces/schema.interface'; +import { CreateSchemaAgentRedirection, GetSchemaAgentRedirection } from './schema.interface'; import { map } from 'rxjs/operators'; -import { - JSONSchemaType, - LedgerLessConstant, - LedgerLessMethods, - OrgAgentType, - SchemaType, - SchemaTypeEnum -} from '@credebl/enum/enum'; -import { - ICredDefWithPagination, - ISchemaData, - ISchemaDetails, - ISchemasWithPagination -} from '@credebl/common/interfaces/schema.interface'; +import { JSONSchemaType, LedgerLessConstant, LedgerLessMethods, OrgAgentType, SchemaType, SchemaTypeEnum } from '@credebl/enum/enum'; +import { ICredDefWithPagination, ISchemaData, ISchemaDetails, ISchemasWithPagination } from '@credebl/common/interfaces/schema.interface'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { CommonConstants } from '@credebl/common/common.constant'; @@ -54,9 +25,6 @@ import { W3CSchemaVersion } from './enum/schema.enum'; import { v4 as uuidv4 } from 'uuid'; import { networkNamespace } from '@credebl/common/common.utils'; import { checkDidLedgerAndNetwork } from '@credebl/common/cast.helper'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { from } from 'rxjs'; -import { w3cSchemaBuilder } from 'apps/ledger/libs/helpers/w3c.schema.builder'; @Injectable() export class SchemaService extends BaseService { @@ -64,110 +32,115 @@ export class SchemaService extends BaseService { private readonly schemaRepository: SchemaRepository, private readonly commonService: CommonService, @Inject('NATS_CLIENT') private readonly schemaServiceProxy: ClientProxy, - // TODO: Remove duplicate, unused variable - @Inject(CACHE_MANAGER) private readonly cacheService: Cache, - private readonly natsClient: NATSClient + @Inject(CACHE_MANAGER) private cacheService: Cache ) { super('SchemaService'); } - async createSchema(schemaDetails: IGenericSchema, user: IUserRequestInterface, orgId: string): Promise { + async createSchema( + schemaDetails: IGenericSchema, + user: IUserRequestInterface, + orgId: string + ): Promise { + const userId = user.id; try { - const { schemaPayload, type, alias } = schemaDetails; - - if (type === SchemaTypeEnum.INDY) { - const schema = schemaPayload as ICreateSchema; - const schemaExists = await this.schemaRepository.schemaExists(schema.schemaName, schema.schemaVersion); - - if (0 !== schemaExists.length) { - this.logger.error(ResponseMessages.schema.error.exists); - throw new ConflictException(ResponseMessages.schema.error.exists, { - cause: new Error(), - description: ResponseMessages.errorMessages.conflict - }); - } - if (null !== schema || schema !== undefined) { - const schemaVersionIndexOf = -1; - if ( + const {schemaPayload, type} = schemaDetails; + + if (type === SchemaTypeEnum.INDY) { + + const schema = schemaPayload as ICreateSchema; + const schemaExists = await this.schemaRepository.schemaExists( + schema.schemaName, + schema.schemaVersion + ); + if (0 !== schemaExists.length) { + this.logger.error(ResponseMessages.schema.error.exists); + throw new ConflictException( + ResponseMessages.schema.error.exists, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + if (null !== schema || schema !== undefined) { + const schemaVersionIndexOf = -1; + if ( isNaN(parseFloat(schema.schemaVersion)) || - schema.schemaVersion.toString().indexOf('.') === schemaVersionIndexOf + schema.schemaVersion.toString().indexOf('.') === + schemaVersionIndexOf ) { - throw new NotAcceptableException(ResponseMessages.schema.error.invalidVersion, { - cause: new Error(), - description: ResponseMessages.errorMessages.notAcceptable - }); + throw new NotAcceptableException( + ResponseMessages.schema.error.invalidVersion, + { cause: new Error(), description: ResponseMessages.errorMessages.notAcceptable } + ); } - + const schemaAttributeLength = 0; if (schema.attributes.length === schemaAttributeLength) { - throw new NotAcceptableException(ResponseMessages.schema.error.insufficientAttributes, { - cause: new Error(), - description: ResponseMessages.errorMessages.notAcceptable - }); - } else if (schema.attributes.length > schemaAttributeLength) { - const trimmedAttributes = schema.attributes.map((attribute) => ({ - attributeName: attribute.attributeName.trim(), - schemaDataType: attribute.schemaDataType, - displayName: attribute.displayName.trim(), - isRequired: attribute.isRequired - })); - - const attributeNamesLowerCase = trimmedAttributes.map((attribute) => attribute.attributeName.toLowerCase()); - const duplicateAttributeNames = attributeNamesLowerCase.filter( - (value, index, element) => element.indexOf(value) !== index - ); - - if (0 < duplicateAttributeNames.length) { - throw new ConflictException(ResponseMessages.schema.error.uniqueAttributesnames, { - cause: new Error(), - description: ResponseMessages.errorMessages.conflict - }); - } - - const attributeDisplayNamesLowerCase = trimmedAttributes.map((attribute) => - attribute.displayName.toLocaleLowerCase() - ); - const duplicateAttributeDisplayNames = attributeDisplayNamesLowerCase.filter( - (value, index, element) => element.indexOf(value) !== index - ); - - if (0 < duplicateAttributeDisplayNames.length) { - throw new ConflictException(ResponseMessages.schema.error.uniqueAttributesDisplaynames, { - cause: new Error(), - description: ResponseMessages.errorMessages.conflict - }); - } - + throw new NotAcceptableException( + ResponseMessages.schema.error.insufficientAttributes, + { cause: new Error(), description: ResponseMessages.errorMessages.notAcceptable } + ); + } else if (schema.attributes.length > schemaAttributeLength) { + + const trimmedAttributes = schema.attributes.map(attribute => ({ + attributeName: attribute.attributeName.trim(), + schemaDataType: attribute.schemaDataType, + displayName: attribute.displayName.trim(), + isRequired: attribute.isRequired + })); + + + const attributeNamesLowerCase = trimmedAttributes.map(attribute => attribute.attributeName.toLowerCase()); + const duplicateAttributeNames = attributeNamesLowerCase + .filter((value, index, element) => element.indexOf(value) !== index); + + if (0 < duplicateAttributeNames.length) { + throw new ConflictException( + ResponseMessages.schema.error.uniqueAttributesnames, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + + const attributeDisplayNamesLowerCase = trimmedAttributes.map(attribute => attribute.displayName.toLocaleLowerCase()); + const duplicateAttributeDisplayNames = attributeDisplayNamesLowerCase + .filter((value, index, element) => element.indexOf(value) !== index); + + if (0 < duplicateAttributeDisplayNames.length) { + throw new ConflictException( + ResponseMessages.schema.error.uniqueAttributesDisplaynames, + { cause: new Error(), description: ResponseMessages.errorMessages.conflict } + ); + } + schema.schemaName = schema.schemaName.trim(); const agentDetails = await this.schemaRepository.getAgentDetailsByOrgId(orgId); if (!agentDetails) { - throw new NotFoundException(ResponseMessages.schema.error.agentDetailsNotFound, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); + throw new NotFoundException( + ResponseMessages.schema.error.agentDetailsNotFound, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } const { agentEndPoint, orgDid } = agentDetails; const getAgentDetails = await this.schemaRepository.getAgentType(orgId); // eslint-disable-next-line yoda const did = schema.orgDid?.split(':').length >= 4 ? schema.orgDid : orgDid; - - const orgAgentType = await this.schemaRepository.getOrgAgentType( - getAgentDetails.org_agents[0].orgAgentTypeId - ); - - const attributeArray = trimmedAttributes.map((item) => item.attributeName); - - const isRequiredAttributeExists = trimmedAttributes.some((attribute) => attribute.isRequired); - - if (!isRequiredAttributeExists) { - throw new BadRequestException(ResponseMessages.schema.error.atLeastOneRequired); - } - + + const orgAgentType = await this.schemaRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); + + const attributeArray = trimmedAttributes.map(item => item.attributeName); + + const isRequiredAttributeExists = trimmedAttributes.some(attribute => attribute.isRequired); + + if (!isRequiredAttributeExists) { + throw new BadRequestException( + ResponseMessages.schema.error.atLeastOneRequired + ); + } + let schemaResponseFromAgentService; if (OrgAgentType.DEDICATED === orgAgentType) { const issuerId = did; - + const schemaPayload = { attributes: attributeArray, version: schema.schemaVersion, @@ -178,9 +151,10 @@ export class SchemaService extends BaseService { agentType: OrgAgentType.DEDICATED }; schemaResponseFromAgentService = await this._createSchema(schemaPayload); + } else if (OrgAgentType.SHARED === orgAgentType) { const { tenantId } = await this.schemaRepository.getAgentDetailsByOrgId(orgId); - + const schemaPayload = { tenantId, method: 'registerSchema', @@ -196,9 +170,9 @@ export class SchemaService extends BaseService { }; schemaResponseFromAgentService = await this._createSchema(schemaPayload); } - + const responseObj = JSON.parse(JSON.stringify(schemaResponseFromAgentService.response)); - + const indyNamespace = `${did.split(':')[2]}:${did.split(':')[3]}`; const getLedgerId = await this.schemaRepository.getLedgerByNamespace(indyNamespace); const schemaDetails: ISchema = { @@ -210,7 +184,7 @@ export class SchemaService extends BaseService { ledgerId: getLedgerId.id, type: SchemaType.INDY }; - + if ('finished' === responseObj.schema.state) { schemaDetails.schema.schemaName = responseObj.schema.schema.name; schemaDetails.schema.attributes = trimmedAttributes; @@ -220,13 +194,16 @@ export class SchemaService extends BaseService { schemaDetails.changedBy = userId; schemaDetails.orgId = orgId; schemaDetails.issuerId = responseObj.schema.schema.issuerId; - const saveResponse = this.schemaRepository.saveSchema(schemaDetails); - + const saveResponse = this.schemaRepository.saveSchema( + schemaDetails + ); + const attributesArray = JSON.parse((await saveResponse).attributes); (await saveResponse).attributes = attributesArray; delete (await saveResponse).lastChangedBy; delete (await saveResponse).lastChangedDateTime; return saveResponse; + } else if ('finished' === responseObj.state) { schemaDetails.schema.schemaName = responseObj.schema.name; schemaDetails.schema.attributes = trimmedAttributes; @@ -236,53 +213,53 @@ export class SchemaService extends BaseService { schemaDetails.changedBy = userId; schemaDetails.orgId = orgId; schemaDetails.issuerId = responseObj.schema.issuerId; - const saveResponse = this.schemaRepository.saveSchema(schemaDetails); - + const saveResponse = this.schemaRepository.saveSchema( + schemaDetails + ); + const attributesArray = JSON.parse((await saveResponse).attributes); (await saveResponse).attributes = attributesArray; delete (await saveResponse).lastChangedBy; delete (await saveResponse).lastChangedDateTime; return saveResponse; + } else { - throw new NotFoundException(ResponseMessages.schema.error.notCreated, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); + throw new NotFoundException( + ResponseMessages.schema.error.notCreated, + { cause: new Error(), description: ResponseMessages.errorMessages.notFound } + ); } } else { - throw new BadRequestException(ResponseMessages.schema.error.emptyData, { - cause: new Error(), - description: ResponseMessages.errorMessages.badRequest - }); + throw new BadRequestException( + ResponseMessages.schema.error.emptyData, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } - } else { - throw new BadRequestException(ResponseMessages.schema.error.emptyData, { - cause: new Error(), - description: ResponseMessages.errorMessages.badRequest - }); + } else { + throw new BadRequestException( + ResponseMessages.schema.error.emptyData, + { cause: new Error(), description: ResponseMessages.errorMessages.badRequest } + ); } - } else if (type === SchemaTypeEnum.JSON) { - const josnSchemaDetails = schemaPayload as unknown as ICreateW3CSchema; - const createW3CSchema = await this.createW3CSchema(orgId, josnSchemaDetails, user.id, alias); + } else if (type === SchemaTypeEnum.JSON) { + const josnSchemaDetails = schemaPayload as unknown as ICreateW3CSchema; + const createW3CSchema = await this.createW3CSchema(orgId, josnSchemaDetails, user.id); return createW3CSchema; - } + } } catch (error) { - this.logger.error(`[createSchema] - outer Error: ${JSON.stringify(error)}`); + this.logger.error( + `[createSchema] - outer Error: ${JSON.stringify(error)}` + ); throw new RpcException(error.response ? error.response : error); } } - async createW3CSchema( - orgId: string, - schemaPayload: ICreateW3CSchema, - user: string, - alias: string - ): Promise { + async createW3CSchema(orgId:string, schemaPayload: ICreateW3CSchema, user: string): Promise { try { let createSchema; - - const { description, attributes, schemaName } = schemaPayload; + + const { description, attributes, schemaName} = schemaPayload; const agentDetails = await this.schemaRepository.getAgentDetailsByOrgId(orgId); if (!agentDetails) { throw new NotFoundException(ResponseMessages.schema.error.agentDetailsNotFound, { @@ -299,8 +276,25 @@ export class SchemaService extends BaseService { description: ResponseMessages.errorMessages.badRequest }); } - const url = `${agentEndPoint}${CommonConstants.CREATE_POLYGON_W3C_SCHEMA}`; - const schemaObject = await w3cSchemaBuilder(attributes, schemaName, description); + + const getAgentDetails = await this.schemaRepository.getAgentType(orgId); + const orgAgentType = await this.schemaRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); + let url; + if (OrgAgentType.DEDICATED === orgAgentType) { + if (agentDetails.orgDid.includes(JSONSchemaType.POLYGON_W3C)) { + url = `${agentEndPoint}${CommonConstants.DEDICATED_CREATE_POLYGON_W3C_SCHEMA}`; + } else if (agentDetails.orgDid.includes(JSONSchemaType.ETHEREUM_W3C)) { + url = `${agentEndPoint}${CommonConstants.DEDICATED_CREATE_ETHEREUM_W3C_SCHEMA}`; + } + } else if (OrgAgentType.SHARED === orgAgentType) { + const { tenantId } = await this.schemaRepository.getAgentDetailsByOrgId(orgId); + if (agentDetails.orgDid.includes(JSONSchemaType.POLYGON_W3C)) { + url = `${agentEndPoint}${CommonConstants.SHARED_CREATE_POLYGON_W3C_SCHEMA}${tenantId}`; + } else if (agentDetails.orgDid.includes(JSONSchemaType.ETHEREUM_W3C)) { + url = `${agentEndPoint}${CommonConstants.SHARED_CREATE_ETHEREUM_W3C_SCHEMA}${tenantId}`; + } + } + const schemaObject = await this.w3cSchemaBuilder(attributes, schemaName, description); if (!schemaObject) { throw new BadRequestException(ResponseMessages.schema.error.schemaBuilder, { cause: new Error(), @@ -308,7 +302,7 @@ export class SchemaService extends BaseService { }); } const agentSchemaPayload = { - schema: schemaObject, + schema:schemaObject, did: agentDetails.orgDid, schemaName }; @@ -317,10 +311,10 @@ export class SchemaService extends BaseService { orgId, schemaRequestPayload: agentSchemaPayload }; - if (schemaPayload.schemaType === JSONSchemaType.POLYGON_W3C) { + if (schemaPayload.schemaType === JSONSchemaType.POLYGON_W3C || schemaPayload.schemaType === JSONSchemaType.ETHEREUM_W3C) { const createSchemaPayload = await this._createW3CSchema(W3cSchemaPayload); createSchema = createSchemaPayload.response; - createSchema.type = JSONSchemaType.POLYGON_W3C; + createSchema.type = schemaPayload.schemaType; } else { const createSchemaPayload = await this._createW3CledgerAgnostic(schemaObject); if (!createSchemaPayload) { @@ -334,16 +328,16 @@ export class SchemaService extends BaseService { createSchema.type = JSONSchemaType.LEDGER_LESS; createSchema.schemaUrl = `${process.env.SCHEMA_FILE_SERVER_URL}${createSchemaPayload.data.schemaId}`; } + + const storeW3CSchema = await this.storeW3CSchemas(createSchema, user, orgId, attributes); - const storeW3CSchema = await this.storeW3CSchemas(createSchema, user, orgId, attributes, alias); - - if (!storeW3CSchema) { - throw new BadRequestException(ResponseMessages.schema.error.storeW3CSchema, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } - + if (!storeW3CSchema) { + throw new BadRequestException(ResponseMessages.schema.error.storeW3CSchema, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } + return storeW3CSchema; } catch (error) { this.logger.error(`[createSchema] - outer Error: ${JSON.stringify(error)}`); @@ -351,101 +345,286 @@ export class SchemaService extends BaseService { } } - private async storeW3CSchemas(schemaDetails, user, orgId, attributes, alias): Promise { + private async w3cSchemaBuilder(attributes, schemaName: string, description: string): Promise { + const schemaAttributeJson = attributes.map((attribute, index) => ({ + [attribute.attributeName]: { + type: attribute.schemaDataType.toLowerCase(), + order: index, + title: attribute.attributeName + } + })); + + // Add the format property to the id key + schemaAttributeJson.unshift({ + id: { + type: 'string', + format: 'uri' + } + }); + + const nestedObject = {}; + schemaAttributeJson.forEach((obj) => { + // eslint-disable-next-line prefer-destructuring + const key = Object.keys(obj)[0]; + nestedObject[key] = obj[key]; + }); + + const schemaNameObject = {}; + schemaNameObject[schemaName] = { + 'const': schemaName + }; + const date = new Date().toISOString(); + + const W3CSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: `${date}-${schemaName}`, + type: 'object', + required: ['@context', 'issuer', 'issuanceDate', 'type', 'credentialSubject'], + properties: { + '@context': { + $ref: '#/definitions/context' + }, + type: { + type: 'array', + items: { + anyOf: [ + { + $ref: '#/definitions/VerifiableCredential' + }, + { + const: `#/definitions/$${schemaName}` + } + ] + } + }, + credentialSubject: { + $ref: '#/definitions/credentialSubject' + }, + id: { + type: 'string', + format: 'uri' + }, + issuer: { + $ref: '#/definitions/uriOrId' + }, + issuanceDate: { + type: 'string', + format: 'date-time' + }, + expirationDate: { + type: 'string', + format: 'date-time' + }, + credentialStatus: { + $ref: '#/definitions/credentialStatus' + }, + credentialSchema: { + $ref: '#/definitions/credentialSchema' + } + }, + definitions: { + context: { + type: 'array', + items: [ + { + const: 'https://www.w3.org/2018/credentials/v1' + } + ], + additionalItems: { + oneOf: [ + { + type: 'string', + format: 'uri' + }, + { + type: 'object' + }, + { + type: 'array', + items: { + $ref: '#/definitions/context' + } + } + ] + }, + minItems: 1, + uniqueItems: true + }, + credentialSubject: { + type: 'object', + required: ['id'], + additionalProperties: false, + properties: nestedObject + }, + VerifiableCredential: { + const: 'VerifiableCredential' + }, + credentialSchema: { + oneOf: [ + { + $ref: '#/definitions/idAndType' + }, + { + type: 'array', + items: { + $ref: '#/definitions/idAndType' + }, + minItems: 1, + uniqueItems: true + } + ] + }, + credentialStatus: { + oneOf: [ + { + $ref: '#/definitions/idAndType' + }, + { + type: 'array', + items: { + $ref: '#/definitions/idAndType' + }, + minItems: 1, + uniqueItems: true + } + ] + }, + idAndType: { + type: 'object', + required: ['id', 'type'], + properties: { + id: { + type: 'string', + format: 'uri' + }, + type: { + type: 'string' + } + } + }, + uriOrId: { + oneOf: [ + { + type: 'string', + format: 'uri' + }, + { + type: 'object', + required: ['id'], + properties: { + id: { + type: 'string', + format: 'uri' + } + } + } + ] + }, + ...schemaNameObject + }, + title: schemaName, + description: `${description}` + }; + return W3CSchema; + } + + private async storeW3CSchemas(schemaDetails, user, orgId, attributes): Promise { let ledgerDetails; - const schemaServerUrl = `${process.env.SCHEMA_FILE_SERVER_URL}${schemaDetails.schemaId}`; - const schemaRequest = await this.commonService.httpGet(schemaServerUrl).then(async (response) => response); + const schemaServerUrl = `${process.env.SCHEMA_FILE_SERVER_URL}${schemaDetails.schemaId}`; + const schemaRequest = await this.commonService + .httpGet(schemaServerUrl) + .then(async (response) => response); if (!schemaRequest) { throw new NotFoundException(ResponseMessages.schema.error.W3CSchemaNotFOund, { cause: new Error(), description: ResponseMessages.errorMessages.notFound }); } - const indyNamespace = await networkNamespace(schemaDetails?.did); - if (indyNamespace === LedgerLessMethods.WEB || indyNamespace === LedgerLessMethods.KEY) { - ledgerDetails = await this.schemaRepository.getLedgerByNamespace(LedgerLessConstant.NO_LEDGER); - } else { - ledgerDetails = await this.schemaRepository.getLedgerByNamespace(indyNamespace); - } + const indyNamespace = await networkNamespace(schemaDetails?.did); + if (indyNamespace === LedgerLessMethods.WEB || indyNamespace === LedgerLessMethods.KEY) { + ledgerDetails = await this.schemaRepository.getLedgerByNamespace(LedgerLessConstant.NO_LEDGER); + } else { + ledgerDetails = await this.schemaRepository.getLedgerByNamespace(indyNamespace); + } - if (!ledgerDetails) { - throw new NotFoundException(ResponseMessages.schema.error.networkNotFound, { - cause: new Error(), - description: ResponseMessages.errorMessages.notFound - }); - } + if (!ledgerDetails) { + throw new NotFoundException(ResponseMessages.schema.error.networkNotFound, { + cause: new Error(), + description: ResponseMessages.errorMessages.notFound + }); + } const storeSchemaDetails = { - schema: { - schemaName: schemaRequest.title, - schemaVersion: W3CSchemaVersion.W3C_SCHEMA_VERSION, - attributes, - id: schemaDetails.schemaUrl - }, + schema: { + schemaName: schemaRequest.title, + schemaVersion: W3CSchemaVersion.W3C_SCHEMA_VERSION, + attributes, + id: schemaDetails.schemaUrl + + }, issuerId: schemaDetails.did, createdBy: user, changedBy: user, publisherDid: schemaDetails.did, orgId, ledgerId: ledgerDetails.id, - type: SchemaType.W3C_Schema, - alias + type: SchemaType.W3C_Schema }; - const saveResponse = await this.schemaRepository.saveSchema(storeSchemaDetails); + const saveResponse = await this.schemaRepository.saveSchema( + storeSchemaDetails + ); return saveResponse; - } - + } + async _createSchema(payload: CreateSchemaAgentRedirection): Promise<{ response: string; }> { - const pattern = { - cmd: 'agent-create-schema' - }; - const schemaResponse = await from(this.natsClient.send(this.schemaServiceProxy, pattern, payload)) - .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { - this.logger.error(`Error in creating schema : ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.error, - message: error.message - }, - error.error - ); - }); - return schemaResponse; + const pattern = { + cmd: 'agent-create-schema' + }; + const schemaResponse = await this.schemaServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { + this.logger.error(`Error in creating schema : ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.statusCode, + error: error.error, + message: error.message + }, error.error); + }); + return schemaResponse; } async _createW3CSchema(payload: W3CCreateSchema): Promise<{ response: string; }> { - const natsPattern = { - cmd: 'agent-create-w3c-schema' - }; - const W3CSchemaResponse = await from(this.natsClient.send(this.schemaServiceProxy, natsPattern, payload)) - .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { - this.logger.error(`Error in creating W3C schema : ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.error.code, - error: error.message, - message: error.error.message.error.message - }, - error.error - ); - }); - return W3CSchemaResponse; + const natsPattern = { + cmd: 'agent-create-w3c-schema' + }; + const W3CSchemaResponse = await this.schemaServiceProxy + .send(natsPattern, payload) + .pipe( + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { + this.logger.error(`Error in creating W3C schema : ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.error.code, + error: error.message, + message: error.error.message.error.message + }, error.error); + }); + return W3CSchemaResponse; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type @@ -468,7 +647,7 @@ export class SchemaService extends BaseService { } } ); - + return jsonldSchemaResponse; } catch (error) { this.logger.error('Error creating W3C ledger agnostic schema:', error); @@ -477,17 +656,18 @@ export class SchemaService extends BaseService { } async getSchemaById(schemaId: string, orgId: string): Promise { - try { - const [{ agentEndPoint }, getAgentDetails, getSchemaDetails] = await Promise.all([ + + try { + const [{agentEndPoint}, getAgentDetails, getSchemaDetails] = await Promise.all([ this.schemaRepository.getAgentDetailsByOrgId(orgId), this.schemaRepository.getAgentType(orgId), this.schemaRepository.getSchemaBySchemaId(schemaId) ]); if (!getSchemaDetails) { - throw new NotFoundException(ResponseMessages.schema.error.notFound); + throw new NotFoundException(ResponseMessages.schema.error.notFound); } - + const orgAgentType = await this.schemaRepository.getOrgAgentType(getAgentDetails.org_agents[0].orgAgentTypeId); let schemaResponse; @@ -514,35 +694,29 @@ export class SchemaService extends BaseService { } return schemaResponse.response; } else if (getSchemaDetails?.type === SchemaType.W3C_Schema) { - return getSchemaDetails; + return getSchemaDetails; } + } catch (error) { this.logger.error(`Error in getting schema by id: ${error}`); - if (error?.status?.message?.error) { + if (error && error?.status && error?.status?.message && error?.status?.message?.error) { throw new RpcException({ - message: error.status.message.error.reason || error.status.message.error, - statusCode: error.status?.code ?? HttpStatus.INTERNAL_SERVER_ERROR + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, + statusCode: error?.status?.code }); - } - throw new RpcException(error.response || error); - } - } - async getSchemaDetails(templateIds: string[]): Promise { - try { - const getSchemaData = await this.schemaRepository.getSchemasDetailsBySchemaIds(templateIds); - return getSchemaData; - } catch (error) { - throw new RpcException(error.response ? error.response : error); + } else { + throw new RpcException(error.response ? error.response : error); + } } } - async getSchemaDetailsBySchemaName(schemaName: string, orgId: string): Promise { - try { - const getSchemaDetails = await this.schemaRepository.getSchemasDetailsBySchemaName(schemaName, orgId); - return getSchemaDetails; + async getSchemaDetails(templateIds: string[]): Promise { + try { + const getSchemaData = await this.schemaRepository.getSchemasDetailsBySchemaIds(templateIds); + return getSchemaData; } catch (error) { - throw new RpcException(error.response ? error.response : error); + throw new RpcException(error.response ? error.response : error); } } @@ -551,22 +725,21 @@ export class SchemaService extends BaseService { const pattern = { cmd: 'agent-get-schema' }; - const schemaResponse = await from(this.natsClient.send(this.schemaServiceProxy, pattern, payload)) + const schemaResponse = await this.schemaServiceProxy + .send(pattern, payload) .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { + map((response) => ( + { + response + })) + ).toPromise() + .catch(error => { this.logger.error(`Catch : ${JSON.stringify(error)}`); throw new HttpException( { status: error.statusCode, error: error.message - }, - error.error - ); + }, error.error); }); return schemaResponse; } catch (error) { @@ -581,23 +754,23 @@ export class SchemaService extends BaseService { if (0 === response.schemasCount) { throw new NotFoundException(ResponseMessages.schema.error.notFound); - } - - const schemasDetails = response?.schemasResult.map((schemaAttributeItem) => { - const attributes = JSON.parse(schemaAttributeItem.attributes); + } + + const schemasDetails = response?.schemasResult.map(schemaAttributeItem => { + const attributes = JSON.parse(schemaAttributeItem.attributes); const firstName = schemaAttributeItem?.['organisation']?.userOrgRoles[0]?.user?.firstName; const orgName = schemaAttributeItem?.['organisation'].name; delete schemaAttributeItem?.['organisation']; - return { - ...schemaAttributeItem, - attributes, - organizationName: orgName, - userName: firstName - }; - }); + return { + ...schemaAttributeItem, + attributes, + organizationName: orgName, + userName: firstName + }; + }); - const nextPage: number = Number(schemaSearchCriteria.pageNumber) + 1; + const nextPage:number = Number(schemaSearchCriteria.pageNumber) + 1; const schemasResponse: ISchemasWithPagination = { totalItems: response.schemasCount, @@ -610,21 +783,24 @@ export class SchemaService extends BaseService { }; return schemasResponse; + } catch (error) { this.logger.error(`Error in retrieving schemas by org id: ${error}`); throw new RpcException(error.response ? error.response : error); } } - async getcredDefListBySchemaId(payload: ISchemaCredDeffSearchInterface): Promise { + async getcredDefListBySchemaId( + payload: ISchemaCredDeffSearchInterface + ): Promise { const { schemaSearchCriteria } = payload; - + try { const response = await this.schemaRepository.getSchemasCredDeffList(schemaSearchCriteria); - + if (0 === response.credDefCount) { throw new NotFoundException(ResponseMessages.schema.error.credentialDefinitionNotFound); - } + } const schemasResponse = { totalItems: response.credDefCount, @@ -637,6 +813,7 @@ export class SchemaService extends BaseService { }; return schemasResponse; + } catch (error) { this.logger.error(`Error in retrieving credential definition: ${error}`); throw new RpcException(error.response ? error.response : error); @@ -646,9 +823,10 @@ export class SchemaService extends BaseService { async getAllSchema(schemaSearchCriteria: ISchemaSearchCriteria): Promise { try { const response = await this.schemaRepository.getAllSchemaDetails(schemaSearchCriteria); - const schemasDetails = response?.schemasResult.map((schemaAttributeItem) => { + + const schemasDetails = response?.schemasResult.map(schemaAttributeItem => { const attributes = JSON.parse(schemaAttributeItem.attributes); - return { ...schemaAttributeItem, attributes, organizationName: schemaAttributeItem.organisation?.name || '' }; + return { ...schemaAttributeItem, attributes }; }); const schemasResponse = { @@ -666,6 +844,8 @@ export class SchemaService extends BaseService { } else { throw new NotFoundException(ResponseMessages.schema.error.notFound); } + + } catch (error) { this.logger.error(`Error in retrieving all schemas: ${error}`); throw new RpcException(error.response ? error.response : error); @@ -675,91 +855,43 @@ export class SchemaService extends BaseService { async _getOrgAgentApiKey(orgId: string): Promise { const pattern = { cmd: 'get-org-agent-api-key' }; const payload = { orgId }; - + try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.natsClient.send(this.schemaServiceProxy, pattern, payload); + const message = await this.schemaServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); } } - async schemaExist(payload: ISchemaExist): Promise< - { - id: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; - name: string; - version: string; - attributes: string; - schemaLedgerId: string; - publisherDid: string; - issuerId: string; - orgId: string; - ledgerId: string; - }[] - > { + async schemaExist(payload: ISchemaExist): Promise<{ + id: string; + createDateTime: Date; + createdBy: string; + lastChangedDateTime: Date; + lastChangedBy: string; + name: string; + version: string; + attributes: string; + schemaLedgerId: string; + publisherDid: string; + issuerId: string; + orgId: string; + ledgerId: string; + }[]> { try { const schemaExist = await this.schemaRepository.schemaExist(payload); return schemaExist; + } catch (error) { this.logger.error(`Error in schema exist: ${error}`); throw new RpcException(error.response ? error.response : error); } } - async archiveSchemas(did: string): Promise { - try { - const schemaDetails = await this.schemaRepository.archiveSchemasByDid(did); - return schemaDetails; - } catch (error) { - this.logger.error(`Error in archive schemas: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async storeSchemaDetails(schemaDetails: ISaveSchema): Promise { - try { - const schemaStoreResult = await this.schemaRepository.saveSchemaRecord(schemaDetails); - return schemaStoreResult; - } catch (error) { - this.logger.error(`Error in storeSchemaDetails: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async getSchemaBySchemaId(schemaId: string): Promise { - try { - const schemaSearchResult = await this.schemaRepository.getSchemaBySchemaId(schemaId); - return schemaSearchResult; - } catch (error) { - this.logger.error(`Error in getSchemaBySchemaId: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async updateSchema(schemaDetails: IUpdateSchema): Promise { - try { - const schemaSearchResult = await this.schemaRepository.updateSchema(schemaDetails); - - if (0 === schemaSearchResult?.count) { - throw new NotFoundException('Records with given condition not found'); - } - - return schemaSearchResult; - } catch (error) { - this.logger.error(`Error in updateSchema: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } -} +} \ No newline at end of file diff --git a/apps/notification/src/main.ts b/apps/notification/src/main.ts index 3ca6db826..cc1d43d0b 100644 --- a/apps/notification/src/main.ts +++ b/apps/notification/src/main.ts @@ -5,8 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { NotificationModule } from '../src/notification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; - const logger = new Logger(); @@ -17,7 +15,7 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.NOTIFICATION_SERVICE, process.env.NOTIFICATION_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/notification/src/notification.module.ts b/apps/notification/src/notification.module.ts index a29ff124e..66a6f151d 100644 --- a/apps/notification/src/notification.module.ts +++ b/apps/notification/src/notification.module.ts @@ -9,10 +9,6 @@ import { NotificationService } from './notification.service'; import { PrismaService } from '@credebl/prisma-service'; import { NotificationRepository } from './notification.repository'; import { CommonConstants } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; @Module({ imports: [ @@ -25,8 +21,6 @@ import { ContextInterceptorModule } from '@credebl/context/contextInterceptorMod } ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, CacheModule.register({ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }) ], controllers: [NotificationController], diff --git a/apps/organization/dtos/client-token.dto.ts b/apps/organization/dtos/client-token.dto.ts deleted file mode 100644 index 52bb2e868..000000000 --- a/apps/organization/dtos/client-token.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class ClientTokenDto { - orgId: string; - clientAlias: string; - clientId: string; - clientSecret: string; - grantType?: string = 'client_credentials'; -} diff --git a/apps/organization/interfaces/organization.interface.ts b/apps/organization/interfaces/organization.interface.ts index 0401e4468..490927617 100644 --- a/apps/organization/interfaces/organization.interface.ts +++ b/apps/organization/interfaces/organization.interface.ts @@ -1,12 +1,11 @@ import { Prisma } from '@prisma/client'; -import { JsonValue } from '@prisma/client/runtime/library'; export interface IUserOrgRoles { - id: string; - userId: string; - orgRoleId: string; - orgId: string | null; - orgRole: IOrgRole; + id: string + userId: string + orgRoleId: string + orgId: string | null, + orgRole: IOrgRole } export interface IClientCredentials { @@ -21,11 +20,9 @@ export interface IUpdateOrganization { logo?: string; website?: string; orgSlug?: string; - isPublic?: boolean; + isPublic?:boolean; userId?: string; - countryId?: number; - cityId?: number; - stateId?: number; + } export interface ICreateConnectionUrl { @@ -45,7 +42,8 @@ export interface IOrgAgent { apiKey: string; } -export interface IGetOrgById { + +export interface IGetOrgById { id: string; name: string; description: string; @@ -63,7 +61,7 @@ interface ISchema { } interface IOrgAgents { - agent_invitations: IAgentInvitation[]; + agent_invitations?: IAgentInvitation[]; ledgers: ILedgers; org_agent_type: IOrgAgentType; } @@ -89,33 +87,33 @@ interface IOrgAgentType { interface ILedgers { id: string; name: string; - networkType: string; + networkType: string } export interface IGetOrganization { - totalCount: number; - totalPages: number; - organizations: IGetAllOrganizations[]; + totalCount:number; + totalPages:number; + organizations : IGetAllOrganizations[]; } -interface IGetAllOrganizations { - id: string; - name: string; - description: string; - logoUrl: string; - orgSlug: string; +interface IGetAllOrganizations{ + id: string, + name: string, + description: string, + logoUrl: string, + orgSlug: string, userOrgRoles: IUserOrganizationRoles[]; } interface IUserOrganizationRoles { - id: string; - orgRole: IOrgRole; + id: string, + orgRole :IOrgRole; } export interface IOrgRole { - id: string; - name: string; - description: string; + id: string + name: string + description: string } export interface IOrgInvitationsPagination { @@ -124,14 +122,14 @@ export interface IOrgInvitationsPagination { } interface IInvitation { - id: string; - orgId: string; - email: string; - userId: string; - status: string; - orgRoles: string[]; - createDateTime: Date; - createdBy: string; + id: string, + orgId: string, + email: string, + userId: string, + status: string, + orgRoles: string[], + createDateTime: Date, + createdBy:string, organisation: IOrganizationPagination; } @@ -157,15 +155,15 @@ export interface IDidList { } export interface IPrimaryDid { - orgId: string; - did: string; + orgId: string, + did: string } export interface IDidDetails { id: string; - createDateTime: Date; + createDateTime: Date; createdBy: string; - lastChangedDateTime: Date; + lastChangedDateTime: Date; lastChangedBy: string; orgId: string; isPrimaryDid: boolean; @@ -175,9 +173,9 @@ export interface IDidDetails { } export interface IPrimaryDidDetails extends IPrimaryDid { - id: string; - networkId: string; - didDocument: Prisma.JsonValue; + id: string + networkId: string + didDocument: Prisma.JsonValue } export interface OrgInvitation { @@ -195,17 +193,17 @@ export interface OrgInvitation { } export interface ILedgerNameSpace { - id: string; - createDateTime: Date; - lastChangedDateTime: Date; - name: string; - networkType: string; - poolConfig: string; - isActive: boolean; - networkString: string; - nymTxnEndpoint: string; - indyNamespace: string; - networkUrl: string; + id: string; + createDateTime: Date; + lastChangedDateTime: Date; + name: string; + networkType: string; + poolConfig: string; + isActive: boolean; + networkString: string; + nymTxnEndpoint: string; + indyNamespace: string; + networkUrl: string; } export interface IGetDids { @@ -233,7 +231,9 @@ export interface ILedgerDetails { nymTxnEndpoint: string; indyNamespace: string; networkUrl: string; + } + export interface IOrgRoleDetails { id: string; name: string; @@ -244,65 +244,10 @@ export interface IOrgRoleDetails { lastChangedBy: string; deletedAt: Date; } -export interface IEcosystemOrgStatus { - ecosystemId: string; - orgId: string; - status: string; -} - -interface IDidDocument { - id: string; - '@context': string[]; - authentication: string[]; - verificationMethod: IVerificationMethod[]; -} export interface IVerificationMethod { id: string; type: string; controller: string; publicKeyBase58: string; -} - -interface IOrgAgentDetails { - id: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; - orgDid: string; - verkey: string | null; - agentEndPoint: string; - agentId: string | null; - isDidPublic: boolean; - agentSpinUpStatus: number; - agentOptions: string | Buffer | null; - walletName: string; - tenantId: string; - apiKey: string | null; - agentsTypeId: string; - orgId: string; - orgAgentTypeId: string; - ledgerId: string; - didDocument: IDidDocument | JsonValue; - webhookUrl: string | null; -} - -interface IOrganisation { - id: string; - name: string; - orgSlug: string; -} - -interface IUserOrgRolesDetails { - id: string; - userId: string; - orgRoleId: string; - orgId: string | null; - idpRoleId: string; -} -export interface IOrgDetails { - organisations: IOrganisation[]; - orgAgents: IOrgAgentDetails[]; - userOrgRoles: IUserOrgRolesDetails[]; -} +} \ No newline at end of file diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index 0cfa681d2..1f6c4d06f 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -1,45 +1,19 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable camelcase */ -import { ConflictException, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; -import { - IDeleteOrganization, - IOrganization, - IOrganizationDashboard, - IOrganizationInvitations -} from '@credebl/common/interfaces/organization.interface'; -import { - IDidDetails, - IDidList, - IGetDids, - IGetOrgById, - IGetOrganization, - ILedgerDetails, - ILedgerNameSpace, - IOrgDetails, - IOrgRoleDetails, - IPrimaryDidDetails, - IUpdateOrganization, - OrgInvitation -} from '../interfaces/organization.interface'; -import { Invitation, PrismaTables, SortValue } from '@credebl/enum/enum'; +import { ConflictException, Injectable, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { - Prisma, - agent_invitations, - org_agents, - org_invitations, - org_roles, - organisation, - user, - user_org_roles -} from '@prisma/client'; +import { Prisma, agent_invitations, org_agents, org_invitations, user, user_org_roles } from '@prisma/client'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; +import { IGetDids, IDidDetails, IDidList, IGetOrgById, IGetOrganization, IPrimaryDidDetails, IUpdateOrganization, ILedgerNameSpace, OrgInvitation, ILedgerDetails, IOrgRoleDetails } from '../interfaces/organization.interface'; +import { InternalServerErrorException } from '@nestjs/common'; +import { Invitation, PrismaTables, SortValue } from '@credebl/enum/enum'; import { PrismaService } from '@credebl/prisma-service'; -import { ResponseMessages } from '@credebl/common/response-messages'; import { UserOrgRolesService } from '@credebl/user-org-roles'; +import { organisation } from '@prisma/client'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { IOrganizationInvitations, IOrganization, IOrganizationDashboard, IDeleteOrganization} from '@credebl/common/interfaces/organization.interface'; @Injectable() export class OrganizationRepository { @@ -47,17 +21,7 @@ export class OrganizationRepository { private readonly prisma: PrismaService, private readonly logger: Logger, private readonly userOrgRoleService: UserOrgRolesService - ) {} - - async getPlatformConfigDetails(): Promise { - try { - const platformConfigdetails = await this.prisma.platform_config.findMany(); - return platformConfigdetails; - } catch (error) { - this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; - } - } + ) { } /** * @@ -78,6 +42,7 @@ export class OrganizationRepository { } } + async checkOrganizationSlugExist(orgSlug: string): Promise { try { return this.prisma.organisation.findUnique({ @@ -93,7 +58,7 @@ export class OrganizationRepository { /** * - * @body CreateOrganizationDto + * @Body createOrgDtp * @returns create Organization */ @@ -118,13 +83,13 @@ export class OrganizationRepository { return orgData; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + throw new error; } } /** * - * @body updateOrgDto + * @Body updateOrgDt0 * @returns update Organization */ @@ -141,15 +106,12 @@ export class OrganizationRepository { website: updateOrgDto.website, orgSlug: updateOrgDto.orgSlug, publicProfile: updateOrgDto.isPublic, - lastChangedBy: updateOrgDto.userId, - countryId: updateOrgDto.countryId, - stateId: updateOrgDto.stateId, - cityId: updateOrgDto.cityId + lastChangedBy: updateOrgDto.userId } }); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + throw new error; } } @@ -170,12 +132,13 @@ export class OrganizationRepository { async updateConnectionInvitationDetails(orgId: string, connectionInvitation: string): Promise { try { const temp = await this.prisma.agent_invitations.updateMany({ - where: { orgId }, + where: {orgId}, data: { connectionInvitation } }); return temp; + } catch (error) { this.logger.error(`Error in updating connection invitation details: ${JSON.stringify(error)}`); throw error; @@ -184,20 +147,23 @@ export class OrganizationRepository { /** * - * @body userOrgRoleDto + * @Body userOrgRoleDto * @returns create userOrgRole */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async createUserOrgRole(userOrgRoleDto): Promise { try { + return this.prisma.user_org_roles.create({ data: { userId: userOrgRoleDto.userId, orgRoleId: userOrgRoleDto.orgRoleId, orgId: userOrgRoleDto.orgId } + }); + } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw new InternalServerErrorException(error); @@ -206,7 +172,7 @@ export class OrganizationRepository { /** * - * @body sendInvitationDto + * @Body sendInvitationDto * @returns orgInvitaionDetails */ @@ -286,6 +252,7 @@ export class OrganizationRepository { } } } + }); } catch (error) { this.logger.error(`error in getOrganizationOwnerDetails: ${JSON.stringify(error)}`); @@ -293,6 +260,7 @@ export class OrganizationRepository { } } + async getAllOrgInvitations( email: string, status: string, @@ -300,6 +268,7 @@ export class OrganizationRepository { pageSize: number, search = '' ): Promise { + this.logger.log(search); const query = { email, @@ -308,7 +277,9 @@ export class OrganizationRepository { return this.getOrgInvitationsPagination(query, pageNumber, pageSize); } - async updateOrganizationById(data: object, orgId: string): Promise { + async updateOrganizationById( + data: object, + orgId: string): Promise { try { const orgDetails = await this.prisma.organisation.update({ where: { id: orgId }, @@ -321,7 +292,10 @@ export class OrganizationRepository { } } - async getOrgInvitations(queryObject: object): Promise { + + async getOrgInvitations( + queryObject: object + ): Promise { try { return this.prisma.org_invitations.findMany({ where: { @@ -337,6 +311,7 @@ export class OrganizationRepository { } } + async getOrgInvitationsCount(orgId: string): Promise { try { return this.prisma.org_invitations.count({ @@ -350,11 +325,7 @@ export class OrganizationRepository { } } - async getOrgInvitationsPagination( - queryObject: object, - pageNumber: number, - pageSize: number - ): Promise { + async getOrgInvitationsPagination(queryObject: object, pageNumber: number, pageSize: number): Promise { try { const result = await this.prisma.$transaction([ this.prisma.org_invitations.findMany({ @@ -402,12 +373,7 @@ export class OrganizationRepository { } } - async getInvitationsByOrgId( - orgId: string, - pageNumber: number, - pageSize: number, - search = '' - ): Promise { + async getInvitationsByOrgId(orgId: string, pageNumber: number, pageSize: number, search = ''): Promise { try { const query = { orgId, @@ -420,21 +386,20 @@ export class OrganizationRepository { return this.getOrgInvitationsPagination(query, pageNumber, pageSize); } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + throw new error; } } async getUser(id: string): Promise { try { const getUserById = await this.prisma.user.findUnique({ - where: { + where:{ id - } - }); + }}); return getUserById; } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + throw new error; } } @@ -477,21 +442,14 @@ export class OrganizationRepository { agentsTypeId: true, orgAgentTypeId: true, createDateTime: true, - tenantId: true, - agent_invitations: { - where: { - multiUse: true - }, - orderBy: { - lastChangedDateTime: SortValue.DESC - }, - take: 1, - select: { - id: true, - connectionInvitation: true, - multiUse: true - } - }, + // Following returns huge response for exceeding NATS max payload size + // agent_invitations: { + // select: { + // id: true, + // connectionInvitation: true, + // multiUse: true + // } + // }, org_agent_type: true, ledgers: { select: { @@ -502,6 +460,7 @@ export class OrganizationRepository { } } } + } }); } catch (error) { @@ -511,6 +470,7 @@ export class OrganizationRepository { } async getOrgDashboard(orgId: string): Promise { + const query = { where: { orgId @@ -518,7 +478,9 @@ export class OrganizationRepository { }; try { - const usersCount = await this.prisma.user.count({ + + const usersCount = await this.prisma.user.count( + { where: { userOrgRoles: { some: { @@ -526,13 +488,11 @@ export class OrganizationRepository { } } } - }); + } + ); const schemasCount = await this.prisma.schema.count({ - where: { - orgId, - isSchemaArchived: false - } + ...query }); const credentialsCount = await this.prisma.credentials.count({ @@ -549,12 +509,14 @@ export class OrganizationRepository { credentialsCount, presentationsCount }; + } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); - throw error; + throw new error; } } + /** * * @param id @@ -601,6 +563,7 @@ export class OrganizationRepository { }); return recordsWithNullIdpId; + } catch (error) { this.logger.error(`error: ${JSON.stringify(error)}`); throw error; @@ -659,10 +622,14 @@ export class OrganizationRepository { logoUrl: true, orgSlug: true, createDateTime: true, - countryId: true, + countryId:true, stateId: true, cityId: true, - appLaunchDetails: true, + ecosystemOrgs: { + select: { + ecosystemId: true + } + }, userOrgRoles: { where: { orgRole: { @@ -680,12 +647,6 @@ export class OrganizationRepository { } } } - }, - org_agents: { - select: { - orgDid: true, - tenantId: true - } } }, take: pageSize, @@ -730,10 +691,10 @@ export class OrganizationRepository { return getOrgCount; } /** - * - * @param name - * @returns Organization exist details - */ + * + * @param name + * @returns Organization exist details + */ async checkOrganizationExist(name: string, orgId: string): Promise { try { @@ -761,37 +722,13 @@ export class OrganizationRepository { throw error; } } - async getOrgAndOwnerUser(orgId: string): Promise { - try { - return this.prisma.user_org_roles.findFirst({ - where: { - orgId, - orgRole: { - name: 'owner' - } - }, - include: { - user: { - select: { - id: true, - keycloakUserId: true - } - } - } - }); - } catch (error) { - this.logger.error(`Error in fetch in organization with admin details`); - throw error; - } - } - async getCredDefByOrg(orgId: string): Promise< - { + + async getCredDefByOrg(orgId: string): Promise<{ tag: string; credentialDefinitionId: string; schemaLedgerId: string; revocable: boolean; - }[] - > { + }[]> { try { return this.prisma.credential_definition.findMany({ where: { @@ -816,6 +753,7 @@ export class OrganizationRepository { async getAgentEndPoint(orgId: string): Promise { try { + const agentDetails = await this.prisma.org_agents.findFirstOrThrow({ where: { orgId @@ -827,18 +765,19 @@ export class OrganizationRepository { } return agentDetails; + } catch (error) { this.logger.error(`Error in get getAgentEndPoint: ${error.message} `); throw error; } } - async deleteOrg(id: string): Promise<{ + async deleteOrg(id: string):Promise<{ deletedUserActivity: Prisma.BatchPayload; deletedUserOrgRole: Prisma.BatchPayload; deletedOrgInvitations: Prisma.BatchPayload; deletedNotification: Prisma.BatchPayload; - deleteOrg: IDeleteOrganization; + deleteOrg: IDeleteOrganization }> { const tablesToCheck = [ `${PrismaTables.ORG_AGENTS}`, @@ -847,6 +786,8 @@ export class OrganizationRepository { `${PrismaTables.CONNECTIONS}`, `${PrismaTables.CREDENTIALS}`, `${PrismaTables.PRESENTATIONS}`, + `${PrismaTables.ECOSYSTEM_INVITATIONS}`, + `${PrismaTables.ECOSYSTEM_ORGS}`, `${PrismaTables.FILE_UPLOAD}` ]; @@ -854,7 +795,7 @@ export class OrganizationRepository { return await this.prisma.$transaction(async (prisma) => { // Check for references in all tables in parallel const referenceCounts = await Promise.all( - tablesToCheck.map((table) => prisma[table].count({ where: { orgId: id } })) + tablesToCheck.map(table => prisma[table].count({ where: { orgId: id } })) ); referenceCounts.forEach((count, index) => { @@ -863,6 +804,20 @@ export class OrganizationRepository { } }); + // Check if the organization is an ecosystem lead + const isEcosystemLead = await prisma.ecosystem_orgs.findMany({ + where: { + orgId: id, + ecosystemRole: { + name: { in: ['Ecosystem Lead', 'Ecosystem Owner'] } + } + } + }); + + if (0 < isEcosystemLead.length) { + throw new ConflictException(ResponseMessages.organisation.error.organizationEcosystemValidate); + } + const deletedNotification = await prisma.notification.deleteMany({ where: { orgId: id } }); const deletedUserActivity = await prisma.user_activity.deleteMany({ where: { orgId: id } }); @@ -884,7 +839,7 @@ export class OrganizationRepository { // If no references are found, delete the organization const deleteOrg = await prisma.organisation.delete({ where: { id } }); - return { deletedUserActivity, deletedUserOrgRole, deletedOrgInvitations, deletedNotification, deleteOrg }; + return {deletedUserActivity, deletedUserOrgRole, deletedOrgInvitations, deletedNotification, deleteOrg}; }); // return result; } catch (error) { @@ -915,10 +870,10 @@ export class OrganizationRepository { async getAllOrganizationDid(orgId: string): Promise { try { return this.prisma.org_dids.findMany({ - where: { + where:{ orgId }, - select: { + select:{ id: true, createDateTime: true, did: true, @@ -934,7 +889,7 @@ export class OrganizationRepository { async setOrgsPrimaryDid(primaryDidDetails: IPrimaryDidDetails): Promise { try { - const { did, didDocument, id, orgId, networkId } = primaryDidDetails; + const {did, didDocument, id, orgId, networkId} = primaryDidDetails; await this.prisma.$transaction([ this.prisma.org_dids.update({ where: { @@ -962,7 +917,7 @@ export class OrganizationRepository { } } - async getDidDetailsByDid(did: string): Promise { +async getDidDetailsByDid(did:string): Promise { try { return this.prisma.org_dids.findFirstOrThrow({ where: { @@ -975,7 +930,7 @@ export class OrganizationRepository { } } - async getPerviousPrimaryDid(orgId: string): Promise { + async getPerviousPrimaryDid(orgId:string): Promise { try { return this.prisma.org_dids.findFirstOrThrow({ where: { @@ -989,7 +944,7 @@ export class OrganizationRepository { } } - async getDids(orgId: string): Promise { + async getDids(orgId:string): Promise { try { return this.prisma.org_dids.findMany({ where: { @@ -1002,7 +957,7 @@ export class OrganizationRepository { } } - async setPreviousDidFlase(id: string): Promise { + async setPreviousDidFlase(id:string): Promise { try { return this.prisma.org_dids.update({ where: { @@ -1081,12 +1036,12 @@ export class OrganizationRepository { userId, orgId }, - select: { + select:{ orgRoleId: true } }); // Map the result to an array of orgRoleId - const orgRoleIds = userOrgRoleDetails.map((role) => role.orgRoleId); + const orgRoleIds = userOrgRoleDetails.map(role => role.orgRoleId); return orgRoleIds; } catch (error) { @@ -1094,132 +1049,4 @@ export class OrganizationRepository { throw error; } } - - async getAgentTypeByAgentTypeId(orgAgentTypeId: string): Promise { - try { - const { agent } = await this.prisma.org_agents_type.findFirst({ - where: { - id: orgAgentTypeId - } - }); - - return agent; - } catch (error) { - this.logger.error(`[getAgentTypeByAgentTypeId] - error: ${JSON.stringify(error)}`); - throw error; - } - } - - async getOrgRoles(roleName: string): Promise { - try { - const orgRoleDetails = await this.prisma.org_roles.findFirstOrThrow({ - where: { - name: roleName - } - }); - - return orgRoleDetails; - } catch (error) { - this.logger.error(`[getOrgRoles] - error: ${JSON.stringify(error)}`); - throw error; - } - } - - async getAllOrgRolesDetails(): Promise { - try { - const orgRoleDetails = await this.prisma.org_roles.findMany(); - return orgRoleDetails; - } catch (error) { - this.logger.error(`[getAllOrgRolesDetails] - error: ${JSON.stringify(error)}`); - throw error; - } - } - - async getOrgRolesById(orgRoles: string[]): Promise { - try { - const roleDetails = await this.prisma.org_roles.findMany({ - where: { - id: { - in: orgRoles - } - }, - select: { - id: true, - name: true, - description: true - } - }); - return roleDetails; - } catch (error) { - this.logger.error(`[getOrgRolesById] - error: ${JSON.stringify(error)}`); - } - } - - async getOrganisationsByIds(organisationIds: string[]): Promise { - try { - const organisations = await this.prisma.organisation.findMany({ - where: { - id: { - in: organisationIds - } - }, - select: { - id: true, - name: true, - orgSlug: true - } - }); - - return organisations; - } catch (error) { - this.logger.error(`Error fetching organisations: ${JSON.stringify(error)}`); - throw error; - } - } - - async handleGetOrganisationData(data: { orgIds: string[]; search: string }): Promise { - try { - const { orgIds, search } = data; - - // Fetch organisation data with optional search filtering - const organisations = await this.prisma.organisation.findMany({ - where: { - id: { in: orgIds }, - OR: [ - { name: { contains: search, mode: 'insensitive' } }, - // eslint-disable-next-line camelcase - { org_agents: { some: { orgDid: { contains: search, mode: 'insensitive' } } } } - ] - }, - select: { - id: true, - name: true, - orgSlug: true - } - }); - - // Fetch org_agents data - const orgAgents = await this.prisma.org_agents.findMany({ - where: { - orgId: { in: orgIds }, - ...(search && { orgDid: { contains: search, mode: 'insensitive' } }) - } - }); - - const userOrgRoles = await this.prisma.user_org_roles.findMany({ - where: { - orgId: { in: orgIds } - } - }); - - return { - organisations, - orgAgents, - userOrgRoles - }; - } catch (error) { - this.logger.error(`Error in handleGetOrganisationData: ${JSON.stringify(error)}`); - throw error; - } - } } diff --git a/apps/organization/src/main.ts b/apps/organization/src/main.ts index a8d0f278e..1ee87db06 100644 --- a/apps/organization/src/main.ts +++ b/apps/organization/src/main.ts @@ -6,7 +6,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; // import { nkeyAuthenticator } from 'nats'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -17,7 +16,6 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.ORGANIZATION_SERVICE, process.env.ORGANIZATION_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/organization/src/organization.controller.ts b/apps/organization/src/organization.controller.ts index 72317bf03..ea5daada5 100644 --- a/apps/organization/src/organization.controller.ts +++ b/apps/organization/src/organization.controller.ts @@ -4,27 +4,11 @@ import { OrganizationService } from './organization.service'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { - IDidList, - IGetOrgById, - IGetOrganization, - IOrgDetails, - IUpdateOrganization, - Payload -} from '../interfaces/organization.interface'; -import { - IOrgCredentials, - IOrganizationInvitations, - IOrganization, - IOrganizationDashboard, - IDeleteOrganization, - IOrgActivityCount -} from '@credebl/common/interfaces/organization.interface'; +import { IDidList, IGetOrgById, IGetOrganization, IUpdateOrganization, Payload } from '../interfaces/organization.interface'; +import { IOrgCredentials, IOrganizationInvitations, IOrganization, IOrganizationDashboard, IDeleteOrganization, IOrgActivityCount } from '@credebl/common/interfaces/organization.interface'; import { organisation, user } from '@prisma/client'; import { IAccessTokenData } from '@credebl/common/interfaces/interface'; import { IClientRoles } from '@credebl/client-registration/interfaces/client.interface'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; -import { ClientTokenDto } from '../dtos/client-token.dto'; @Controller() export class OrganizationController { @@ -38,9 +22,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'create-organization' }) - async createOrganization( - @Body() payload: { createOrgDto: CreateOrganizationDto; userId: string; keycloakUserId: string } - ): Promise { + async createOrganization(@Body() payload: { createOrgDto: CreateOrganizationDto; userId: string, keycloakUserId: string }): Promise { return this.organizationService.createOrganization(payload.createOrgDto, payload.userId, payload.keycloakUserId); } @@ -51,19 +33,17 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'set-primary-did' }) - async setPrimaryDid(@Body() payload: { orgId: string; did: string; id: string }): Promise { + async setPrimaryDid(@Body() payload: { orgId:string, did:string, id:string}): Promise { return this.organizationService.setPrimaryDid(payload.orgId, payload.did, payload.id); } /** - * - * @param payload + * + * @param payload * @returns organization client credentials */ @MessagePattern({ cmd: 'create-org-credentials' }) - async createOrgCredentials( - @Body() payload: { orgId: string; userId: string; keycloakUserId: string } - ): Promise { + async createOrgCredentials(@Body() payload: { orgId: string; userId: string, keycloakUserId: string }): Promise { return this.organizationService.createOrgCredentials(payload.orgId, payload.userId, payload.keycloakUserId); } @@ -74,11 +54,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-organization' }) - async updateOrganization(payload: { - updateOrgDto: IUpdateOrganization; - userId: string; - orgId: string; - }): Promise { + async updateOrganization(payload: { updateOrgDto: IUpdateOrganization; userId: string, orgId: string }): Promise { return this.organizationService.updateOrganization(payload.updateOrgDto, payload.userId, payload.orgId); } @@ -88,7 +64,7 @@ export class OrganizationController { * @returns organization's did list */ @MessagePattern({ cmd: 'fetch-organization-dids' }) - async getOrgDidList(payload: { orgId: string }): Promise { + async getOrgDidList(payload: {orgId:string}): Promise { return this.organizationService.getOrgDidList(payload.orgId); } @@ -98,7 +74,9 @@ export class OrganizationController { * @returns Get created organization details */ @MessagePattern({ cmd: 'get-organizations' }) - async getOrganizations(@Body() payload: { userId: string } & Payload): Promise { + async getOrganizations( + @Body() payload: { userId: string} & Payload + ): Promise { const { userId, pageNumber, pageSize, search, role } = payload; return this.organizationService.getOrganizations(userId, pageNumber, pageSize, search, role); } @@ -108,9 +86,12 @@ export class OrganizationController { * @returns Get created organization details */ @MessagePattern({ cmd: 'get-organizations-count' }) - async countTotalOrgs(@Body() payload: { userId: string }): Promise { + async countTotalOrgs( + @Body() payload: { userId: string} + ): Promise { + const { userId } = payload; - + return this.organizationService.countTotalOrgs(userId); } @@ -118,7 +99,9 @@ export class OrganizationController { * @returns Get public organization details */ @MessagePattern({ cmd: 'get-public-organizations' }) - async getPublicOrganizations(@Body() payload: Payload): Promise { + async getPublicOrganizations( + @Body() payload: Payload + ): Promise { const { pageNumber, pageSize, search } = payload; return this.organizationService.getPublicOrganizations(pageNumber, pageSize, search); } @@ -129,13 +112,13 @@ export class OrganizationController { * @returns Get created organization details */ @MessagePattern({ cmd: 'get-organization-by-id' }) - async getOrganization(@Body() payload: { orgId: string; userId: string }): Promise { + async getOrganization(@Body() payload: { orgId: string; userId: string}): Promise { return this.organizationService.getOrganization(payload.orgId); } - /** - * @param orgSlug - * @returns organization details - */ +/** + * @param orgSlug + * @returns organization details + */ @MessagePattern({ cmd: 'get-organization-public-profile' }) async getPublicProfile(payload: { orgSlug }): Promise { return this.organizationService.getPublicProfile(payload); @@ -147,7 +130,9 @@ export class OrganizationController { * @returns Get created invitation details */ @MessagePattern({ cmd: 'get-invitations-by-orgId' }) - async getInvitationsByOrgId(@Body() payload: { orgId: string } & Payload): Promise { + async getInvitationsByOrgId( + @Body() payload: { orgId: string } & Payload + ): Promise { return this.organizationService.getInvitationsByOrgId( payload.orgId, payload.pageNumber, @@ -157,11 +142,11 @@ export class OrganizationController { } /** - * @returns Get org-roles + * @returns Get org-roles */ @MessagePattern({ cmd: 'get-org-roles' }) - async getOrgRoles(payload: { orgId: string; user: user }): Promise { + async getOrgRoles(payload: {orgId: string, user: user}): Promise { return this.organizationService.getOrgRoles(payload.orgId, payload.user); } @@ -177,7 +162,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'send-invitation' }) async createInvitation( - @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string; userEmail: string } + @Body() payload: { bulkInvitationDto: BulkSendInvitationDto; userId: string, userEmail: string } ): Promise { return this.organizationService.createInvitation(payload.bulkInvitationDto, payload.userId, payload.userEmail); } @@ -212,7 +197,7 @@ export class OrganizationController { */ @MessagePattern({ cmd: 'update-user-roles' }) - async updateUserRoles(payload: { orgId: string; roleIds: string[]; userId: string }): Promise { + async updateUserRoles(payload: { orgId: string; roleIds: string[]; userId: string}): Promise { return this.organizationService.updateUserRoles(payload.orgId, payload.roleIds, payload.userId); } @@ -226,9 +211,9 @@ export class OrganizationController { return this.organizationService.getOrganizationActivityCount(payload.orgId, payload.userId); } - /** - * @returns organization profile details - */ +/** + * @returns organization profile details + */ @MessagePattern({ cmd: 'fetch-organization-profile' }) async getOrgPofile(payload: { orgId: string }): Promise { return this.organizationService.getOrgPofile(payload.orgId); @@ -245,67 +230,27 @@ export class OrganizationController { } @MessagePattern({ cmd: 'get-organization-details' }) - async getOrgData(payload: { orgId: string }): Promise { + async getOrgData(payload: { orgId: string; }): Promise { return this.organizationService.getOrgDetails(payload.orgId); } - + @MessagePattern({ cmd: 'delete-organization' }) - async deleteOrganization(payload: { orgId: string; user: user }): Promise { + async deleteOrganization(payload: { orgId: string, user: user }): Promise { return this.organizationService.deleteOrganization(payload.orgId, payload.user); } @MessagePattern({ cmd: 'delete-org-client-credentials' }) - async deleteOrganizationCredentials(payload: { orgId: string; user: user }): Promise { + async deleteOrganizationCredentials(payload: { orgId: string, user: user }): Promise { return this.organizationService.deleteClientCredentials(payload.orgId, payload.user); } @MessagePattern({ cmd: 'delete-organization-invitation' }) - async deleteOrganizationInvitation(payload: { orgId: string; invitationId: string }): Promise { + async deleteOrganizationInvitation(payload: { orgId: string; invitationId: string; }): Promise { return this.organizationService.deleteOrganizationInvitation(payload.orgId, payload.invitationId); } @MessagePattern({ cmd: 'authenticate-client-credentials' }) - async clientLoginCredentails(payload: { clientId: string; clientSecret: string }): Promise { + async clientLoginCredentails(payload: { clientId: string; clientSecret: string;}): Promise { return this.organizationService.clientLoginCredentails(payload); } - - @MessagePattern({ cmd: 'get-platform-config-details' }) - async getPlatformConfigDetails(): Promise { - return this.organizationService.getPlatformConfigDetails(); - } - - @MessagePattern({ cmd: 'get-agent-type-by-org-agent-type-id' }) - async getAgentTypeByAgentTypeId(payload: { orgAgentTypeId: string }): Promise { - return this.organizationService.getAgentTypeByAgentTypeId(payload.orgAgentTypeId); - } - - @MessagePattern({ cmd: 'get-org-roles-details' }) - async getOrgRolesDetails(payload: { roleName: string }): Promise { - return this.organizationService.getOrgRolesDetails(payload.roleName); - } - - @MessagePattern({ cmd: 'get-all-org-roles-details' }) - async getAllOrgRoles(): Promise { - return this.organizationService.getAllOrgRoles(); - } - - @MessagePattern({ cmd: 'get-org-roles-by-id' }) - async getOrgRolesDetailsByIds(orgRoles: string[]): Promise { - return this.organizationService.getOrgRolesDetailsByIds(orgRoles); - } - - @MessagePattern({ cmd: 'get-organization-by-org-id' }) - async getOrganisationsByIds(payload: { organisationIds }): Promise { - return this.organizationService.getOrganisationsByIds(payload.organisationIds); - } - - @MessagePattern({ cmd: 'get-org-agents-and-user-roles' }) - async getOrgAgentDetailsForEcosystem(payload: { orgIds: string[]; search: string }): Promise { - return this.organizationService.getOrgAgentDetailsForEcosystem(payload); - } - - @MessagePattern({ cmd: 'generate-client-api-token' }) - async generateClientApiToken(payload: ClientTokenDto): Promise<{ token: string }> { - return this.organizationService.generateClientApiToken(payload); - } } diff --git a/apps/organization/src/organization.module.ts b/apps/organization/src/organization.module.ts index 37ad1c270..a7d0aae8c 100644 --- a/apps/organization/src/organization.module.ts +++ b/apps/organization/src/organization.module.ts @@ -17,14 +17,8 @@ import { CacheModule } from '@nestjs/cache-manager'; import { getNatsOptions } from '@credebl/common/nats.config'; import { ClientRegistrationService } from '@credebl/client-registration'; import { KeycloakUrlService } from '@credebl/keycloak-url'; - import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ ClientsModule.register([ @@ -35,8 +29,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; } ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, CacheModule.register() ], controllers: [OrganizationController], @@ -46,8 +38,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; UserActivityRepository, UserOrgRolesRepository, UserRepository, UserActivityService, ClientRegistrationService, KeycloakUrlService, - AwsService, - NATSClient + AwsService ] }) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 81bcc313e..f3011a6e1 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -9,36 +9,25 @@ import { HttpException, BadRequestException, ForbiddenException, - UnauthorizedException, - NotFoundException, - Inject + UnauthorizedException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; import { CommonService } from '@credebl/common'; import { OrganizationRepository } from '../repositories/organization.repository'; import { ClientProxy, RpcException } from '@nestjs/microservices'; +import { Inject, NotFoundException } from '@nestjs/common'; import { OrgRolesService } from '@credebl/org-roles'; import { OrgRoles } from 'libs/org-roles/enums'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { ResponseMessages } from '@credebl/common/response-messages'; import { OrganizationInviteTemplate } from '../templates/organization-invitation.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { DidMethod, Invitation, Ledgers, PrismaTables, SessionType, TokenType, transition } from '@credebl/enum/enum'; -import { - IGetOrgById, - IGetOrganization, - IUpdateOrganization, - IClientCredentials, - ICreateConnectionUrl, - IOrgRole, - IDidList, - IPrimaryDidDetails, - IEcosystemOrgStatus, - IOrgDetails -} from '../interfaces/organization.interface'; +import { DidMethod, Invitation, Ledgers, PrismaTables, transition } from '@credebl/enum/enum'; +import { IGetOrgById, IGetOrganization, IUpdateOrganization, IOrgAgent, IClientCredentials, ICreateConnectionUrl, IOrgRole, IDidList, IPrimaryDidDetails } from '../interfaces/organization.interface'; import { UserActivityService } from '@credebl/user-activity'; import { ClientRegistrationService } from '@credebl/client-registration/client-registration.service'; import { map } from 'rxjs/operators'; @@ -60,13 +49,6 @@ import { IClientRoles } from '@credebl/client-registration/interfaces/client.int import { toNumber } from '@credebl/common/cast.helper'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { DeleteOrgInvitationsEmail } from '../templates/delete-organization-invitations.template'; -import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { UserRepository } from 'apps/user/repositories/user.repository'; -import * as jwt from 'jsonwebtoken'; -import { ClientTokenDto } from '../dtos/client-token.dto'; -import { EmailService } from '@credebl/common/email.service'; - @Injectable() export class OrganizationService { constructor( @@ -79,24 +61,11 @@ export class OrganizationService { private readonly awsService: AwsService, private readonly userActivityService: UserActivityService, private readonly logger: Logger, - // TODO: Remove duplicate, unused variable @Inject(CACHE_MANAGER) private cacheService: Cache, private readonly clientRegistrationService: ClientRegistrationService, - private readonly userActivityRepository: UserActivityRepository, - private readonly natsClient: NATSClient, - private readonly userRepository: UserRepository, - private readonly emailService: EmailService + private readonly userActivityRepository: UserActivityRepository ) {} - - async getPlatformConfigDetails(): Promise { - try { - const getPlatformDetails = await this.organizationRepository.getPlatformConfigDetails(); - return getPlatformDetails; - } catch (error) { - this.logger.error(`In fetch getPlatformConfigDetails : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } + /** * @@ -111,10 +80,10 @@ export class OrganizationService { keycloakUserId: string ): Promise { try { - const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); - + const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); + if (userOrgCount >= toNumber(`${process.env.MAX_ORG_LIMIT}`)) { - throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); + throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); } const organizationExist = await this.organizationRepository.checkOrganizationNameExist(createOrgDto.name); @@ -129,7 +98,7 @@ export class OrganizationService { if (isOrgSlugExist) { throw new ConflictException(ResponseMessages.organisation.error.exists); - } + } createOrgDto.orgSlug = orgSlug; createOrgDto.createdBy = userId; @@ -142,6 +111,7 @@ export class OrganizationService { createOrgDto.logo = ''; } + const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); // To return selective object data @@ -165,12 +135,12 @@ export class OrganizationService { clientId, idpId }; - + const updatedOrg = await this.organizationRepository.updateOrganizationById( updateOrgData, organizationDetails.id ); - + if (!updatedOrg) { throw new InternalServerErrorException(ResponseMessages.organisation.error.credentialsNotUpdate); } @@ -197,63 +167,59 @@ export class OrganizationService { } } - private async ensureOrganizationExists(orgId: string): Promise { - const organizationExist = await this.organizationRepository.getOrgProfile(orgId); - if (!organizationExist) { - throw new NotFoundException(ResponseMessages.organisation.error.notFound); - } - } - private async ensureNotExistingPrimaryDid(orgId: string, did: string): Promise { - const orgAgentDetails = await this.organizationRepository.getAgentEndPoint(orgId); - if (orgAgentDetails.orgDid === did) { - throw new ConflictException(ResponseMessages.organisation.error.primaryDid); - } - } - private async ensureDidBelongsToOrg(orgId: string, did: string): Promise { - const organizationDidList = await this.organizationRepository.getAllOrganizationDid(orgId); - const isDidMatch = organizationDidList.some((item) => item.did === did); - if (!isDidMatch) { - throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); - } - } - /** + /** * * @param registerOrgDto * @returns */ // eslint-disable-next-line camelcase - async setPrimaryDid(orgId: string, did: string, id: string): Promise { + async setPrimaryDid( + orgId:string, + did:string, + id:string + ): Promise { try { - await this.ensureOrganizationExists(orgId); - await this.ensureNotExistingPrimaryDid(orgId, did); + const organizationExist = await this.organizationRepository.getOrgProfile(orgId); + if (!organizationExist) { + throw new NotFoundException(ResponseMessages.organisation.error.notFound); + } + const orgAgentDetails = await this.organizationRepository.getAgentEndPoint(orgId); + if (orgAgentDetails.orgDid === did) { + throw new ConflictException(ResponseMessages.organisation.error.primaryDid); + } //check user DID exist in the organization's did list - await this.ensureDidBelongsToOrg(orgId, did); + const organizationDidList = await this.organizationRepository.getAllOrganizationDid(orgId); + const isDidMatch = organizationDidList.some(item => item.did === did); + + if (!isDidMatch) { + throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); + } const didDetails = await this.organizationRepository.getDidDetailsByDid(did); if (!didDetails) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + const dids = await this.organizationRepository.getDids(orgId); - const noPrimaryDid = dids.every((orgDids) => false === orgDids.isPrimaryDid); + const noPrimaryDid = dids.every(orgDids => false === orgDids.isPrimaryDid); let existingPrimaryDid; let priviousDidFalse; if (!noPrimaryDid) { existingPrimaryDid = await this.organizationRepository.getPerviousPrimaryDid(orgId); - + if (!existingPrimaryDid) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + priviousDidFalse = await this.organizationRepository.setPreviousDidFlase(existingPrimaryDid.id); - } + } const didParts = did.split(':'); let nameSpace: string | null = null; - + // This condition will handle the multi-ledger support if (DidMethod.INDY === didParts[1]) { nameSpace = `${didParts[2]}:${didParts[3]}`; @@ -285,7 +251,9 @@ export class OrganizationService { await Promise.all([setPrimaryDid, existingPrimaryDid, priviousDidFalse]); + return ResponseMessages.organisation.success.primaryDid; + } catch (error) { this.logger.error(`In setPrimaryDid method: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -309,11 +277,9 @@ export class OrganizationService { let generatedClientSecret = ''; if (organizationDetails.idpId) { + const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret - ); + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); generatedClientSecret = await this.clientRegistrationService.generateClientSecret( organizationDetails.idpId, @@ -324,6 +290,7 @@ export class OrganizationService { clientSecret: this.maskString(generatedClientSecret) }; } else { + try { const orgCredentials = await this.registerToKeycloak( organizationDetails.name, @@ -332,11 +299,11 @@ export class OrganizationService { userId, true ); - + const { clientId, idpId, clientSecret } = orgCredentials; - + generatedClientSecret = clientSecret; - + updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -379,17 +346,14 @@ export class OrganizationService { shouldUpdateRole: boolean ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret - ); + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); const orgDetails = await this.clientRegistrationService.createClient(orgName, orgId, token); const orgRolesList = [OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER]; - for (const role of orgRolesList) { - await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); - } + for (const role of orgRolesList) { + await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); + } const ownerRoleClient = await this.clientRegistrationService.getClientSpecificRoles( orgDetails.idpId, @@ -407,18 +371,20 @@ export class OrganizationService { const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); if (!shouldUpdateRole) { + await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.createUserOrgRole(userId, ownerRoleData.id, orgId, ownerRoleClient.id) ]); + } else { const roleIdList = [ { roleId: ownerRoleData.id, idpRoleId: ownerRoleClient.id } - ]; - + ]; + await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.deleteOrgRoles(userId, orgId), @@ -493,7 +459,7 @@ export class OrganizationService { try { const updatedOrglogo = orgLogo.split(',')[1]; const imgData = Buffer.from(updatedOrglogo, 'base64'); - const logoUrl = await this.awsService.uploadFileToS3Bucket( + const logoUrl = await this.awsService.uploadUserCertificate( imgData, 'png', 'orgLogo', @@ -530,6 +496,7 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: string, orgId: string): Promise { try { + const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); if (organizationExist && organizationExist.id !== orgId) { @@ -551,24 +518,13 @@ export class OrganizationService { const checkAgentIsExists = await this.organizationRepository.getAgentInvitationDetails(orgId); if (!checkAgentIsExists?.connectionInvitation && !checkAgentIsExists?.agentId) { - organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); - } else if ( - organizationDetails?.logoUrl !== organizationExist?.logoUrl || - organizationDetails?.name !== organizationExist?.name - ) { + organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); + } else if (organizationDetails?.logoUrl !== organizationExist?.logoUrl || organizationDetails?.name !== organizationExist?.name) { const invitationData = await this._createConnection(updateOrgDto?.logo, updateOrgDto?.name, orgId); - await this.organizationRepository.updateConnectionInvitationDetails( - orgId, - invitationData?.connectionInvitation - ); + await this.organizationRepository.updateConnectionInvitationDetails(orgId, invitationData?.connectionInvitation); } - await this.userActivityService.createActivity( - userId, - organizationDetails.id, - `${organizationDetails.name} organization updated`, - 'Organization details updated successfully' - ); + await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); @@ -576,7 +532,12 @@ export class OrganizationService { } } - async _createConnection(orgName: string, logoUrl: string, orgId: string): Promise { + + async _createConnection( + orgName: string, + logoUrl: string, + orgId: string + ): Promise { const pattern = { cmd: 'create-connection-invitation' }; const payload = { @@ -586,9 +547,9 @@ export class OrganizationService { orgId } }; - const connectionInvitationData = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const connectionInvitationData = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -603,8 +564,12 @@ export class OrganizationService { return connectionInvitationData; } - async countTotalOrgs(userId: string): Promise { + async countTotalOrgs( + userId: string + + ): Promise { try { + const getOrgs = await this.organizationRepository.userOrganizationCount(userId); return getOrgs; } catch (error) { @@ -612,7 +577,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + /** * @returns Get created organizations details */ @@ -629,7 +594,7 @@ export class OrganizationService { userOrgRoles: { some: { userId } }, - OR: [ + OR: [ { name: { contains: search, mode: 'insensitive' } }, { description: { contains: search, mode: 'insensitive' } } ] @@ -639,152 +604,35 @@ export class OrganizationService { userId }; - const getOrgs = await this.organizationRepository.getOrganizations( - query, - filterOptions, - pageNumber, - pageSize, - role, - userId - ); - - const { organizations } = getOrgs; - - if (0 === organizations?.length) { - throw new NotFoundException(ResponseMessages.organisation.error.organizationNotFound); - } - - let orgIds; - let updatedOrgs; - - if ('true' === process.env.IS_ECOSYSTEM_ENABLE) { - orgIds = organizations?.map((item) => item.id); - - const orgEcosystemDetails = await this._getOrgEcosystems(orgIds); - - updatedOrgs = getOrgs.organizations.map((org) => { - const matchingEcosystems = orgEcosystemDetails - .filter((ecosystem) => ecosystem.orgId === org.id) - .map((ecosystem) => ({ ecosystemId: ecosystem.ecosystemId })); - return { - ...org, - ecosystemOrgs: 0 < matchingEcosystems.length ? matchingEcosystems : [] - }; - }); - } else { - updatedOrgs = getOrgs?.organizations?.map((org) => ({ - ...org - })); - } - - return { - totalCount: getOrgs.totalCount, - totalPages: getOrgs.totalPages, - organizations: updatedOrgs - }; + const getOrgs = await this.organizationRepository.getOrganizations(query, filterOptions, pageNumber, pageSize, role, userId); + return getOrgs; } catch (error) { this.logger.error(`In fetch getOrganizations : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async _getOrgEcosystems(orgIds: string[]): Promise { - const pattern = { cmd: 'get-ecosystems-by-org' }; - - const payload = { orgIds }; - - const response = await this.organizationServiceProxy - .send(pattern, payload) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - return response; - } - - /** - * Method used for generate access token based on client-id and client secret - * @param clientCredentials - * @returns session and access token both - */ async clientLoginCredentails(clientCredentials: IClientCredentials): Promise { - const { clientId, clientSecret } = clientCredentials; - // This method used to authenticate the requested user on keycloak - const authenticationResult = await this.authenticateClientKeycloak(clientId, clientSecret); - let addSessionDetails; - // Fetch owner organization details for getting the user id - const orgRoleDetails = await this.organizationRepository.getOrgAndOwnerUser(clientId); - // called seprate method to delete exp session - this.userRepository.deleteInactiveSessions(orgRoleDetails['user'].id); - - // Fetch the total number of sessions for the requested user to check and restrict the creation of multiple sessions. - const userSessionDetails = await this.userRepository.fetchUserSessions(orgRoleDetails['user'].id); - if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { - throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const decodedToken: any = jwt.decode(authenticationResult?.access_token); - const expiresAt = new Date(decodedToken.exp * 1000); - // Session payload - const sessionData = { - sessionToken: authenticationResult?.access_token, - userId: orgRoleDetails['user'].id, - expires: authenticationResult?.expires_in, - sessionType: SessionType.ORG_SESSION, - expiresAt - }; - // Note: - // Fetch account details to check whether the requested user account exists - // If the account exists, update it with the latest details and create a new session - // Otherwise, create a new account and also create the new session - const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id); - if (fetchAccountDetails) { - const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - } else { - // Note: - // This else block is mostly used for already registered users on the platform to create their account & session in the database. - // Once all users are migrated or created their accounts and sessions in the DB, this code can be removed. - const accountData = { - userId: orgRoleDetails['user'].id, - keycloakUserId: orgRoleDetails['user'].keycloakUserId, - type: TokenType.BEARER_TOKEN - }; - - await this.userRepository.addAccountDetails(accountData).then(async (response) => { - const finalSessionData = { ...sessionData, accountId: response.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - }); - } - // Response: add session id - const finalResponse = { - ...authenticationResult, - sessionId: addSessionDetails.id - }; - return finalResponse; - } + const {clientId, clientSecret} = clientCredentials; + return this.authenticateClientKeycloak(clientId, clientSecret); +} async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { + try { - const payload = new ClientCredentialTokenPayloadDto(); - // eslint-disable-next-line camelcase - payload.client_id = clientId; - // eslint-disable-next-line camelcase - payload.client_secret = clientSecret; + const payload = new ClientCredentialTokenPayloadDto(); + // eslint-disable-next-line camelcase + payload.client_id = clientId; + // eslint-disable-next-line camelcase + payload.client_secret = clientSecret; + + try { + const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); + return mgmtTokenResponse; + } catch (error) { + throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); + } - try { - const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); - return mgmtTokenResponse; - } catch (error) { - throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); - } } catch (error) { this.logger.error(`Error in authenticateClientKeycloak : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -961,35 +809,36 @@ export class OrganizationService { userEmail: string, userId: string, orgName: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; - for (const invitation of invitations) { - const { orgRoleId, email } = invitation; - - const isUserExist = await this.checkUserExistInPlatform(email); + for (const invitation of invitations) { + const { orgRoleId, email } = invitation; - const userData = await this.getUserFirstName(userEmail); + const isUserExist = await this.checkUserExistInPlatform(email); - const { firstName } = userData; - const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); + const userData = await this.getUserFirstName(userEmail); + + const {firstName} = userData; + const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); + + if (0 === orgRolesDetails.length) { + throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); + } - if (0 === orgRolesDetails.length) { - throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); - } + const isInvitationExist = await this.checkInvitationExist(email, orgId); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + if (!isInvitationExist && userEmail !== invitation.email) { - if (!isInvitationExist && userEmail !== invitation.email) { - await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); + await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); - try { - await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); - } catch (error) { - throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); + try { + await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); + } } } - } } async createInvitationByClientRoles( @@ -998,14 +847,11 @@ export class OrganizationService { userId: string, orgName: string, idpId: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret - ); + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); @@ -1031,6 +877,7 @@ export class OrganizationService { const isInvitationExist = await this.checkInvitationExist(email, orgId); if (!isInvitationExist && userEmail !== invitation.email) { + await this.organizationRepository.createSendInvitation( email, String(orgId), @@ -1039,7 +886,13 @@ export class OrganizationService { ); try { - await this.sendInviteEmailTemplate(email, orgName, filteredOrgRoles, firstName, isUserExist); + await this.sendInviteEmailTemplate( + email, + orgName, + filteredOrgRoles, + firstName, + isUserExist + ); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -1049,7 +902,7 @@ export class OrganizationService { /** * - * @body sendInvitationDto + * @Body sendInvitationDto * @returns createInvitation */ @@ -1064,7 +917,12 @@ export class OrganizationService { } if (!organizationDetails.idpId) { - await this.createInvitationByOrgRoles(bulkInvitationDto, userEmail, userId, organizationDetails.name); + await this.createInvitationByOrgRoles( + bulkInvitationDto, + userEmail, + userId, + organizationDetails.name + ); } else { await this.createInvitationByClientRoles( bulkInvitationDto, @@ -1120,7 +978,7 @@ export class OrganizationService { ); //Email is sent to user for the verification through emailData - const isEmailSent = await this.emailService.sendEmail(emailData); + const isEmailSent = await sendEmail(emailData); return isEmailSent; } @@ -1129,9 +987,9 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-mail' }; const payload = { email }; - const userData: user = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const userData: user = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1152,9 +1010,9 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-mail' }; const payload = { email: userEmail }; - const userData = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const userData = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1172,17 +1030,20 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-user-id' }; // const payload = { id: userId }; - const userData = await this.natsClient.send(this.organizationServiceProxy, pattern, userId).catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.error, - message: error.message - }, - error.status - ); - }); + const userData = await this.organizationServiceProxy + .send(pattern, userId) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.error, + message: error.message + }, + error.status + ); + }); return userData; } @@ -1211,45 +1072,38 @@ export class OrganizationService { status: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret - ); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); + const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); - const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { - let roleObj: { roleId: string; name: string; idpRoleId: string } = null; + const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { + let roleObj: { roleId: string; name: string; idpRoleId: string} = null; - for (let index = 0; index < clientRolesList.length; index++) { - if (clientRolesList[index].name === orgRole.name) { - roleObj = { - roleId: orgRole.id, - name: orgRole.name, - idpRoleId: clientRolesList[index].id - }; - break; + for (let index = 0; index < clientRolesList.length; index++) { + if (clientRolesList[index].name === orgRole.name) { + roleObj = { + roleId: orgRole.id, + name: orgRole.name, + idpRoleId: clientRolesList[index].id + }; + break; + } } - } - return roleObj; - }); + return roleObj; + }); - const data = { - status - }; + const data = { + status + }; + + await Promise.all([ + this.organizationRepository.updateOrgInvitation(invitation.id, data), + this.clientRegistrationService.createUserClientRole(idpId, token, keycloakUserId, rolesPayload.map(role => ({id: role.idpRoleId, name: role.name}))), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) + ]); - await Promise.all([ - this.organizationRepository.updateOrgInvitation(invitation.id, data), - this.clientRegistrationService.createUserClientRole( - idpId, - token, - keycloakUserId, - rolesPayload.map((role) => ({ id: role.idpRoleId, name: role.name })) - ), - this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) - ]); } /** @@ -1324,11 +1178,11 @@ export class OrganizationService { orgId: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles( + idpId, + token ); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); const matchedClientRoles = clientRolesList.filter((role) => roleIds.includes(role.id.trim())); @@ -1359,7 +1213,11 @@ export class OrganizationService { const userData = await this.getUserUserId(userId); const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles(idpId, token, userData.keycloakUserId), + this.clientRegistrationService.deleteUserClientRoles( + idpId, + token, + userData.keycloakUserId + ), this.userOrgRoleService.deleteOrgRoles(userId, orgId) ]); @@ -1420,8 +1278,15 @@ export class OrganizationService { return true; } else { - return this.updateUserClientRoles(roleIds, organizationDetails.idpId, userId, organizationDetails.id); + + return this.updateUserClientRoles( + roleIds, + organizationDetails.idpId, + userId, + organizationDetails.id + ); } + } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1437,26 +1302,28 @@ export class OrganizationService { } } + async getOrganizationActivityCount(orgId: string, userId: string): Promise { try { - const [verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgInvitationsCount, orgUsers] = - await Promise.all([ - this._getVerificationRecordsCount(orgId, userId), - this._getIssuanceRecordsCount(orgId, userId), - this._getConnectionRecordsCount(orgId, userId), - this.organizationRepository.getOrgInvitationsCount(orgId), - this.organizationRepository.getOrgDashboard(orgId) - ]); - - const orgUsersCount = orgUsers?.['usersCount']; - - return { + const [ verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, - orgUsersCount, - orgInvitationsCount - }; + orgInvitationsCount, + orgUsers, + orgEcosystemsCount + ] = await Promise.all([ + this._getVerificationRecordsCount(orgId, userId), + this._getIssuanceRecordsCount(orgId, userId), + this._getConnectionRecordsCount(orgId, userId), + this.organizationRepository.getOrgInvitationsCount(orgId), + this.organizationRepository.getOrgDashboard(orgId), + this._getEcosystemsCount(orgId, userId) + ]); + + const orgUsersCount = orgUsers?.['usersCount']; + + return {verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgUsersCount, orgEcosystemsCount, orgInvitationsCount}; } catch (error) { this.logger.error(`In fetch organization references count : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1470,18 +1337,19 @@ export class OrganizationService { orgId, userId }; - const ecosystemsCount = await ( - this.natsClient.send(this.organizationServiceProxy, pattern, payload) as unknown as Promise - ).catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); + const ecosystemsCount = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); return ecosystemsCount; } @@ -1493,9 +1361,9 @@ export class OrganizationService { orgId, userId }; - const connectionsCount = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const connectionsCount = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1510,6 +1378,7 @@ export class OrganizationService { return connectionsCount; } + async _getIssuanceRecordsCount(orgId: string, userId: string): Promise { const pattern = { cmd: 'get-issuance-records' }; @@ -1517,9 +1386,9 @@ export class OrganizationService { orgId, userId }; - const issuanceCount = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const issuanceCount = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1541,9 +1410,9 @@ export class OrganizationService { orgId, userId }; - const verificationCount = await this.natsClient - .send(this.organizationServiceProxy, pattern, payload) - + const verificationCount = await this.organizationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1606,7 +1475,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + async deleteOrganization(orgId: string, user: user): Promise { try { const getUser = await this.organizationRepository.getUser(user?.id); @@ -1615,128 +1484,113 @@ export class OrganizationService { this.clientRegistrationService.getManagementToken(getUser?.clientId, getUser?.clientSecret), this.organizationRepository.getOrganizationDetails(orgId) ]); - + if (!organizationDetails) { throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - + const organizationInvitationDetails = await this.organizationRepository.getOrgInvitationsByOrg(orgId); - - const arrayEmail = organizationInvitationDetails.map((userData) => userData.email); + + const arrayEmail = organizationInvitationDetails.map(userData => userData.email); this.logger.debug(`arrayEmail ::: ${JSON.stringify(arrayEmail)}`); - + // Fetch Keycloak IDs only if there are emails to process - const keycloakUserIds = - 0 < arrayEmail.length - ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.keycloakUserId) - : []; - + const keycloakUserIds = 0 < arrayEmail.length + ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.keycloakUserId) + : []; + this.logger.log('Keycloak User Ids'); // Delete user client roles in parallel - const deleteUserRolesPromises = keycloakUserIds.map((keycloakUserId) => - this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) + const deleteUserRolesPromises = keycloakUserIds.map(keycloakUserId => this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) ); deleteUserRolesPromises.push( this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, getUser?.keycloakUserId) ); - + this.logger.debug(`deleteUserRolesPromises ::: ${JSON.stringify(deleteUserRolesPromises)}`); const deleteUserRolesResults = await Promise.allSettled(deleteUserRolesPromises); - + // Check for failures in deleting user roles - const deletionFailures = deleteUserRolesResults.filter((result) => 'rejected' === result?.status); - + const deletionFailures = deleteUserRolesResults.filter(result => 'rejected' === result?.status); + if (0 < deletionFailures.length) { this.logger.error(`deletionFailures ::: ${JSON.stringify(deletionFailures)}`); throw new NotFoundException(ResponseMessages.organisation.error.orgDataNotFoundInkeycloak); } + + const deletedOrgInvitationInfo: { email?: string, orgName?: string, orgRoleNames?: string[] }[] = []; + const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.id); + await Promise.all(userIds.map(async (userId) => { + const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); + this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); - const deletedOrgInvitationInfo: { email?: string; orgName?: string; orgRoleNames?: string[] }[] = []; - const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.id); - await Promise.all( - userIds.map(async (userId) => { - const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); - this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); - - const userDetails = await this.organizationRepository.getUser(userId); - this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); - - const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); - this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); - - const orgRoleNames = orgRoles.map((orgRoleName) => orgRoleName.name); - const sendEmail = await this.sendEmailForOrgInvitationsMember( - userDetails?.email, - organizationDetails?.name, - orgRoleNames - ); - const newInvitation = { - email: userDetails.email, - orgName: organizationDetails?.name, - orgRoleNames - }; - - // Step 3: Push the data into the array - deletedOrgInvitationInfo.push(newInvitation); - - this.logger.log( - `email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}` - ); - }) - ); - + const userDetails = await this.organizationRepository.getUser(userId); + this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); + + const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); + this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); + + const orgRoleNames = orgRoles.map(orgRoleName => orgRoleName.name); + const sendEmail = await this.sendEmailForOrgInvitationsMember(userDetails?.email, organizationDetails?.name, orgRoleNames); + const newInvitation = { + email: userDetails.email, + orgName: organizationDetails?.name, + orgRoleNames + }; + + // Step 3: Push the data into the array + deletedOrgInvitationInfo.push(newInvitation); + + this.logger.log(`email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}`); + })); + // Delete organization data - const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = - await this.organizationRepository.deleteOrg(orgId); - + const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = await this.organizationRepository.deleteOrg(orgId); + this.logger.debug(`deletedUserActivity ::: ${JSON.stringify(deletedUserActivity)}`); this.logger.debug(`deletedUserOrgRole ::: ${JSON.stringify(deletedUserOrgRole)}`); this.logger.debug(`deleteOrg ::: ${JSON.stringify(deleteOrg)}`); this.logger.debug(`deletedOrgInvitations ::: ${JSON.stringify(deletedOrgInvitations)}`); - + const deletions = [ { records: deletedUserActivity.count, tableName: `${PrismaTables.USER_ACTIVITY}` }, { records: deletedUserOrgRole.count, tableName: `${PrismaTables.USER_ORG_ROLES}` }, - { - records: deletedOrgInvitations.count, - deletedOrgInvitationInfo, - tableName: `${PrismaTables.ORG_INVITATIONS}` - }, + { records: deletedOrgInvitations.count, deletedOrgInvitationInfo, tableName: `${PrismaTables.ORG_INVITATIONS}` }, { records: deletedNotification.count, tableName: `${PrismaTables.NOTIFICATION}` }, { records: deleteOrg ? 1 : 0, tableName: `${PrismaTables.ORGANIZATION}` } ]; - + // Log deletion activities in parallel - await Promise.all( - deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { - if (records) { - const txnMetadata: { - deletedRecordsCount: number; - deletedRecordInTable: string; - deletedOrgInvitationInfo?: object[]; - } = { - deletedRecordsCount: records, - deletedRecordInTable: tableName - }; - - if (deletedOrgInvitationInfo) { - txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; - } - - const recordType = RecordType.ORGANIZATION; - await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); + await Promise.all(deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { + if (records) { + const txnMetadata: { + deletedRecordsCount: number; + deletedRecordInTable: string; + deletedOrgInvitationInfo?: object[] + } = { + deletedRecordsCount: records, + deletedRecordInTable: tableName + }; + + if (deletedOrgInvitationInfo) { + txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; } - }) - ); - + + const recordType = RecordType.ORGANIZATION; + await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); + } + })); + return deleteOrg; + } catch (error) { this.logger.error(`delete organization: ${JSON.stringify(error)}`); throw new RpcException(error.response ?? error); } } + async sendEmailForOrgInvitationsMember(email: string, orgName: string, orgRole: string[]): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -1746,14 +1600,50 @@ export class OrganizationService { emailData.emailTo = email; emailData.emailSubject = `Removal of participation of “${orgName}”`; - emailData.emailHtml = urlEmailTemplate.sendDeleteOrgMemberEmailTemplate(email, orgName, orgRole); + emailData.emailHtml = await urlEmailTemplate.sendDeleteOrgMemberEmailTemplate( + email, + orgName, + orgRole + ); //Email is sent to user for the verification through emailData - const isEmailSent = await this.emailService.sendEmail(emailData); + const isEmailSent = await sendEmail(emailData); return isEmailSent; } + async _deleteWallet(payload: IOrgAgent): Promise<{ + response; + }> { + try { + const pattern = { + cmd: 'delete-wallet' + }; + + return this.organizationServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ({ + response + })) + ) + .toPromise() + .catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.statusCode, + error: error.message + }, + error.error + ); + }); + } catch (error) { + this.logger.error(`[_deleteWallet] - error in delete wallet : ${JSON.stringify(error)}`); + throw error; + } + } + async getUserKeycloakIdByEmail(userEmails: string[]): Promise<{ response; }> { @@ -1763,7 +1653,7 @@ export class OrganizationService { }; return this.organizationServiceProxy - .send(pattern, userEmails) + .send(pattern, userEmails) .pipe( map((response: string) => ({ response @@ -1792,7 +1682,7 @@ export class OrganizationService { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.organizationServiceProxy.send(pattern, payload).toPromise(); + const message = await this.organizationServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); @@ -1807,19 +1697,20 @@ export class OrganizationService { } async registerOrgsMapUsers(): Promise { + try { - const unregisteredOrgsList = await this.organizationRepository.getUnregisteredClientOrgs(); + const unregisteredOrgsList = await this.organizationRepository.getUnregisteredClientOrgs(); + if (!unregisteredOrgsList || 0 === unregisteredOrgsList.length) { throw new NotFoundException('Unregistered client organizations not found'); - } + } for (const org of unregisteredOrgsList) { const userOrgRoles = 0 < org['userOrgRoles'].length && org['userOrgRoles']; - const ownerUserList = - 0 < org['userOrgRoles'].length && - userOrgRoles.filter((userOrgRole) => userOrgRole.orgRole.name === OrgRoles.OWNER); + const ownerUserList = 0 < org['userOrgRoles'].length + && userOrgRoles.filter(userOrgRole => userOrgRole.orgRole.name === OrgRoles.OWNER); const ownerUser = 0 < ownerUserList.length && ownerUserList[0].user; @@ -1842,7 +1733,7 @@ export class OrganizationService { ); const { clientId, idpId, clientSecret } = orgCredentials; - + const updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -1850,59 +1741,58 @@ export class OrganizationService { }; const updatedOrg = await this.organizationRepository.updateOrganizationById(updateOrgData, orgObj.id); - + this.logger.log(`updatedOrg::`, updatedOrg); - const usersToRegisterList = userOrgRoles.filter((userOrgRole) => null !== userOrgRole.user.keycloakUserId); - - const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); - const token = await this.clientRegistrationService.getManagementToken( - userDetails.clientId, - userDetails.clientSecret - ); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - - const deletedUserDetails: string[] = []; - for (const userRole of usersToRegisterList) { - const user = userRole.user; - - const matchedClientRoles = clientRolesList - .filter((role) => userRole.orgRole.name === role.name) - .map((clientRole) => ({ roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name })); - - if (!deletedUserDetails.includes(user.id)) { - const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), - this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) + const usersToRegisterList = userOrgRoles.filter(userOrgRole => null !== userOrgRole.user.keycloakUserId); + + const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); + const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + + const deletedUserDetails: string[] = []; + for (const userRole of usersToRegisterList) { + const user = userRole.user; + + const matchedClientRoles = clientRolesList.filter((role) => userRole.orgRole.name === role.name) + .map(clientRole => ({roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name})); + + if (!deletedUserDetails.includes(user.id)) { + const [, deletedUserRoleRecords] = await Promise.all([ + this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), + this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) + ]); + + this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); + + deletedUserDetails.push(user.id); + } + + + await Promise.all([ + this.clientRegistrationService.createUserClientRole( + idpId, + token, + user.keycloakUserId, + matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole( + user.id, + orgObj.id, + matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) + ) ]); + this.logger.log(`Organization client created and users mapped to roles`); - this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); - - deletedUserDetails.push(user.id); - } - - await Promise.all([ - this.clientRegistrationService.createUserClientRole( - idpId, - token, - user.keycloakUserId, - matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) - ), - this.userOrgRoleService.updateUserOrgRole( - user.id, - orgObj.id, - matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) - ) - ]); - this.logger.log(`Organization client created and users mapped to roles`); - } - } + } + } } - + return ''; } catch (error) { this.logger.error(`Error in registerOrgsMapUsers: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); + } } @@ -1943,7 +1833,7 @@ export class OrganizationService { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.natsClient.send(this.organizationServiceProxy, pattern, payload); + const message = await this.organizationServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); @@ -1970,132 +1860,4 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - - async getAgentTypeByAgentTypeId(orgAgentTypeId: string): Promise { - try { - return await this.organizationRepository.getAgentTypeByAgentTypeId(orgAgentTypeId); - } catch (error) { - this.logger.error(`get getAgentTypeByAgentTypeId error: ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async getOrgRolesDetails(roleName: string): Promise { - try { - const orgRoleDetails = await this.organizationRepository.getOrgRoles(roleName); - return orgRoleDetails; - } catch (error) { - this.logger.error(`in getting organization role details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async getAllOrgRoles(): Promise { - try { - const orgRoleDetails = await this.organizationRepository.getAllOrgRolesDetails(); - return orgRoleDetails; - } catch (error) { - this.logger.error(`in getting all organization roles : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async getOrgRolesDetailsByIds(orgRoles: string[]): Promise { - try { - const orgRoleDetails = await this.organizationRepository.getOrgRolesById(orgRoles); - return orgRoleDetails; - } catch (error) { - this.logger.error(`in getting org roles by id : ${JSON.stringify(error)}`); - } - } - - async getOrganisationsByIds(organisationIds): Promise { - try { - return await this.organizationRepository.getOrganisationsByIds(organisationIds); - } catch (error) { - this.logger.error(`get getOrganisationsByIds error: ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async getOrgAgentDetailsForEcosystem(data: { orgIds: string[]; search: string }): Promise { - try { - const getAllOrganizationDetails = await this.organizationRepository.handleGetOrganisationData(data); - - if (!getAllOrganizationDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } - - return getAllOrganizationDetails; - } catch (error) { - this.logger.error(`Error in getOrgAgentDetailsForEcosystem: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async generateClientApiToken(generateTokenDetails: ClientTokenDto): Promise<{ token: string }> { - try { - // Fetch organization and fail fast if not found - const orgDetails = await this.organizationRepository.getOrganizationDetails(generateTokenDetails.orgId); - if (!orgDetails) { - throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); - } - - // Compose keys - const clientIdKey = `${generateTokenDetails.clientAlias}_KEYCLOAK_MANAGEMENT_CLIENT_ID`; - const clientSecretKey = `${generateTokenDetails.clientAlias}_KEYCLOAK_MANAGEMENT_CLIENT_SECRET`; - - // Fetch env vars once - const encryptedClientId = process.env[clientIdKey]; - const encryptedClientSecret = process.env[clientSecretKey]; - - // Fail fast if not present - if (!encryptedClientId || !encryptedClientSecret) { - throw new NotFoundException(ResponseMessages.organisation.error.invalidClientCredentials); - } - - // Decrypt env vars once - const decryptedClientId = await this.commonService.decryptPassword(encryptedClientId); - if (!decryptedClientId) { - throw new NotFoundException(ResponseMessages.organisation.error.invalidClientCredentials); - } - const decryptedSecret = await this.commonService.decryptPassword(encryptedClientSecret); - if (!decryptedSecret) { - throw new NotFoundException(ResponseMessages.organisation.error.invalidClientCredentials); - } - - // Guard clauses for validation - if (generateTokenDetails.clientAlias.toLowerCase() !== decryptedClientId.toLowerCase()) { - throw new NotFoundException(ResponseMessages.organisation.error.invalidClientCredentials); - } - if (generateTokenDetails.clientSecret !== decryptedSecret) { - throw new NotFoundException(ResponseMessages.organisation.error.invalidClientCredentials); - } - - // Generate admin token - const adminTokenDetails = - await this.clientRegistrationService.generateTokenUsingAdminCredentials(generateTokenDetails); - if (!adminTokenDetails?.access_token) { - throw new InternalServerErrorException(ResponseMessages.organisation.error.adminTokenDetails); - } - - // Fetch client details - const clientDetails = await this.clientRegistrationService.fetchClientDetails( - generateTokenDetails.orgId, - adminTokenDetails.access_token - ); - if (!clientDetails?.length) { - throw new NotFoundException(ResponseMessages.organisation.error.clientDetails); - } - - // Authenticate client - const { secret } = clientDetails[0]; - const authenticationResult = await this.authenticateClientKeycloak(generateTokenDetails.orgId, secret); - - return { token: authenticationResult.access_token }; - } catch (error) { - this.logger.error(`in generating issuer api token: ${JSON.stringify(error)}`); - throw new RpcException(error.response || error); - } - } } diff --git a/apps/organization/templates/organization-invitation.template.ts b/apps/organization/templates/organization-invitation.template.ts index b40a05ab8..e3fb38f58 100644 --- a/apps/organization/templates/organization-invitation.template.ts +++ b/apps/organization/templates/organization-invitation.template.ts @@ -1,23 +1,25 @@ export class OrganizationInviteTemplate { - public sendInviteEmailTemplate( - email: string, - orgName: string, - orgRolesDetails: object[], - firstName: string, - isUserExist: boolean - ): string { - const validUrl = isUserExist ? `${process.env.FRONT_END_URL}/sign-in` : `${process.env.FRONT_END_URL}/sign-up`; - const message = isUserExist - ? `Please accept the invitation using the following link:` - : `To get started, kindly register on ${process.env.PLATFORM_NAME} platform using this link:`; + public sendInviteEmailTemplate( + email: string, + orgName: string, + orgRolesDetails: object[], + firstName: string, + isUserExist: boolean + ): string { - const secondMessage = isUserExist - ? `After successful login into ${process.env.PLATFORM_NAME} click on "Accept Organization Invitation" link on your dashboard.` - : `After successful registration, you can log in to the platform and click on “Accept Organization Invitation” on your dashboard.`; + const validUrl = isUserExist ? `${process.env.FRONT_END_URL}/authentication/sign-in` : `${process.env.FRONT_END_URL}/authentication/sign-up`; - const Button = isUserExist ? `Accept Organization Invitation` : `Register on ${process.env.PLATFORM_NAME}`; + const message = isUserExist + ? `Please accept the invitation using the following link:` + : `To get started, kindly register on ${process.env.PLATFORM_NAME} platform using this link:`; + const secondMessage = isUserExist + ? `After successful login into ${process.env.PLATFORM_NAME} click on "Accept Organization Invitation" link on your dashboard.` + : `After successful registration, you can log in to the platform and click on “Accept Organization Invitation” on your dashboard.`; + + const Button = isUserExist ? `Accept Organization Invitation` : `Register on ${process.env.PLATFORM_NAME}`; + return ` @@ -73,5 +75,8 @@ export class OrganizationInviteTemplate { `; - } -} + + } + + +} \ No newline at end of file diff --git a/apps/user/dtos/login-user.dto.ts b/apps/user/dtos/login-user.dto.ts index 0c344161b..fbfb32528 100644 --- a/apps/user/dtos/login-user.dto.ts +++ b/apps/user/dtos/login-user.dto.ts @@ -1,29 +1,42 @@ -import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; - +import { trim } from '@credebl/common/cast.helper'; import { ApiProperty } from '@nestjs/swagger'; -import type { Prisma } from '@prisma/client'; import { Transform } from 'class-transformer'; -import { trim } from '@credebl/common/cast.helper'; +import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class LoginUserDto { - @ApiProperty({ example: 'awqx@yopmail.com' }) - @IsEmail({}, { message: 'Please provide a valid email' }) - @IsNotEmpty({ message: 'Email is required' }) - @IsString({ message: 'Email should be a string' }) - @Transform(({ value }) => trim(value)) - email: string; + @ApiProperty({ example: 'awqx@getnada.com' }) + @IsEmail({}, { message: 'Please provide a valid email' }) + @IsNotEmpty({ message: 'Email is required' }) + @IsString({ message: 'Email should be a string' }) + @Transform(({ value }) => trim(value)) + email: string; + + @ApiProperty({ example: 'Password@1' }) + @IsOptional() + @IsString({ message: 'password should be string' }) + password: string; + + @ApiProperty({ example: 'false' }) + @IsOptional() + @IsBoolean({ message: 'isPasskey should be boolean' }) + isPasskey: boolean; +} - @ApiProperty({ example: 'Password@1' }) - @IsOptional() - @IsString({ message: 'password should be string' }) - password?: string; - @ApiProperty({ example: 'false' }) - @IsOptional() - @IsBoolean({ message: 'isPasskey should be boolean' }) - isPasskey?: boolean; +export class LoginUserNameDto { + @ApiProperty({ example: '098f6bcd4621d373cade4e832627b4f8' }) + @IsNotEmpty({ message: 'username is required' }) + @IsString({ message: 'username should be a string' }) + @Transform(({ value }) => trim(value)) + username: string; + + @ApiProperty({ example: 'Password@1' }) + @IsOptional() + @IsString({ message: 'password should be string' }) + password: string; - @ApiProperty({ example: 'false' }) - @IsOptional() - clientInfo?: Prisma.JsonValue; -} + @ApiProperty({ example: 'false' }) + @IsOptional() + @IsBoolean({ message: 'isPasskey should be boolean' }) + isPasskey: boolean; +} \ No newline at end of file diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 9c2e02465..52d9141a8 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -12,20 +12,20 @@ export interface IUsersProfile { } interface IUserOrgRole { - id: string; - userId: string; - orgRoleId: string; - orgId: string; - orgRole: IOrgRole; - organisation: IOrganisation; -} -export interface IOrgRole { - id: string; + id: string; + userId: string; + orgRoleId: string; + orgId: string; + orgRole :IOrgRole; + organisation:IOrganisation; +} + export interface IOrgRole{ + id: string; name: string; description: string; -} -export interface IOrganisation { - id: string; + }; + export interface IOrganisation{ + id: string; name: string; description: string; orgSlug: string; @@ -64,6 +64,17 @@ export interface IUserInformation { isHolder?: boolean; } +export interface IUserInformationUsernameBased { + username: string; + password: string; + firstName: string; + lastName: string; + isPasskey: boolean; + isHolder?: boolean; + clientId?: string; + clientSecret?: string; +} + export interface AddPasskeyDetails { password: string; } @@ -81,6 +92,8 @@ export interface PlatformSettings { sgApiKey: string; emailFrom: string; apiEndPoint: string; + enableEcosystem: boolean; + multiEcosystemSupport: boolean; } export interface IShareUserCertificate { @@ -105,13 +118,19 @@ export interface ICheckUserDetails { isEmailVerified?: boolean; isFidoVerified?: boolean; isRegistrationCompleted?: boolean; - userId?: number; - message?: string; +} +export interface IUserCredentials { + id: string; + imageUrl?: string; + credentialId?: string; + createDateTime: Date; + lastChangedDateTime: Date; + deletedAt: Date; } export interface IOrgUsers { - totalPages: number; - users: OrgUser[]; + totalPages: number, + users: OrgUser[] } export interface IDidList { @@ -137,7 +156,7 @@ interface UserOrgRoles { orgId: string; orgRoleId: string; orgRole: OrgRole; - organisation: Organization; + organisation: Organization } interface OrgRole { id: string; @@ -146,22 +165,22 @@ interface OrgRole { } interface Organization { - id: string; - name: string; - description: string; - orgSlug: string; - logoUrl: string; + id: string, + name: string, + description: string, + orgSlug: string, + logoUrl: string, org_agents: OrgAgents[]; } interface OrgAgents { - id: string; - orgDid: string; - walletName: string; - agentSpinUpStatus: number; - agentsTypeId: string; - createDateTime: Date; - orgAgentTypeId: string; + id: string, + orgDid: string, + walletName: string, + agentSpinUpStatus: number, + agentsTypeId: string, + createDateTime: Date, + orgAgentTypeId:string } export interface Payload { @@ -170,59 +189,31 @@ export interface Payload { search: string; } -export interface IVerifyUserEmail { +export interface IVerifyUserEmail{ email: string; verificationCode: string; } -export interface IUserSignIn { +export interface IUserSignIn{ email: string; password: string; - isPasskey?: boolean; - clientInfo: Prisma.JsonValue; -} - -export interface ISession { - id?: string; - sessionToken?: string; - userId?: string; - expires?: number; - refreshToken?: string; - keycloakUserId?: string; - type?: string; - accountId?: string; - sessionType?: string; - expiresAt?: Date; - clientInfo?: Prisma.JsonValue | null; + isPasskey: boolean; } -export interface IUpdateAccountDetails { - accessToken: string; - refreshToken?: string; - expiresAt: number; - accountId: string; -} -export interface ISessionDetails extends ISession { - id: string; - createdAt: Date; - updatedAt: Date; +export interface IUserNameSignIn{ + username: string; + password: string; + isPasskey: boolean; } -export interface IUserResetPassword { +export interface IUserResetPassword{ email: string; oldPassword?: string; newPassword?: string; token?: string; password?: string; } -export interface IUserForgotPassword { - email: string; - brandLogoUrl?: string; - platformName?: string; - endpoint?: string; - clientAlias?: string; -} export interface IIssueCertificate { courseCode: string; courseName: string; @@ -231,7 +222,7 @@ export interface IIssueCertificate { practicalGradeCredits: string; practicalObtainedEarned: string; } -export interface IPuppeteerOption { +export interface IPuppeteerOption{ width: number; height: number; } @@ -258,54 +249,7 @@ export interface UserRoleMapping { userRoleId: string; } -export interface ISessions { - sessions: string[]; -} -export interface UserRoleDetails { +export interface UserRoleDetails{ id: string; role: $Enums.UserRole; -} - -export interface IEcosystemConfig { - id: string; - key: string; - value: string; - createDateTime: Date; - createdBy: string; - lastChangedDateTime: Date; - lastChangedBy: string; - deletedAt: Date | null; -} - -export interface IAccountDetails { - userId: string; - type?: string; - provider?: string; - providerAccountId?: string; - refresh_token?: string; - access_token?: string; - expires_at?: string; - scope?: string; - token_type?: string; - id_token?: string; - session_state?: string; -} -export interface ISessionData { - sessionId: string; -} - -export interface IRestrictedUserSession { - id: string; - userId: string; - expiresAt: Date; - createdAt: Date; - clientInfo: Prisma.JsonValue | null; - sessionType: string; -} - -export interface ITokenData { - sessionToken: string; - expires: number; - refreshToken: string; - expiresAt: Date; -} +} \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 3889e96c2..3c9c24cb9 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -1,45 +1,27 @@ -/* eslint-disable camelcase */ /* eslint-disable prefer-destructuring */ -import { - BadRequestException, - Injectable, - InternalServerErrorException, - Logger, - NotFoundException -} from '@nestjs/common'; +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { IOrgUsers, - IRestrictedUserSession, - ISendVerificationEmail, - ISession, + PlatformSettings, IShareUserCertificate, - ITokenData, - IUserDeletedActivity, - IUserInformation, + UpdateUserProfile, + IUserCredentials, + ISendVerificationEmail, IUsersProfile, + IUserInformation, IVerifyUserEmail, - PlatformSettings, - UpdateUserProfile, + IUserDeletedActivity, UserKeycloakId, + UserRoleMapping, UserRoleDetails, - UserRoleMapping + IUserInformationUsernameBased } from '../interfaces/user.interface'; -import { - Prisma, - RecordType, - account, - client_aliases, - schema, - session, - token, - user, - user_org_roles -} from '@prisma/client'; -import { ProviderType, UserRole } from '@credebl/enum/enum'; - +import { InternalServerErrorException } from '@nestjs/common'; import { PrismaService } from '@credebl/prisma-service'; -import { RpcException } from '@nestjs/microservices'; +// eslint-disable-next-line camelcase +import { RecordType, schema, token, user } from '@prisma/client'; +import { UserRole } from '@credebl/enum/enum'; interface UserQueryOptions { id?: string; // Use the appropriate type based on your data model @@ -55,21 +37,6 @@ export class UserRepository { private readonly logger: Logger ) {} - /** - * - * @returns Client alias and its url - */ - - // eslint-disable-next-line camelcase - async fetchClientAliases(): Promise { - try { - return this.prisma.client_aliases.findMany(); - } catch (error) { - this.logger.error(`checkUserExist: ${JSON.stringify(error)}`); - throw error; - } - } - /** * * @param userEmailVerification @@ -101,6 +68,36 @@ export class UserRepository { } } + deleteUser(userId:string): Promise { + + return this.prisma.user.delete({ + where:{ + id: userId + } + }); + } + + + async createUserWithoutVerification(user: IUserInformationUsernameBased): Promise { + try { + const saveResponse = await this.prisma.user.create({ + data: { + username: user.username, + clientId: user.clientId, + clientSecret: user.clientSecret, + firstName: user.firstName, + lastName: user.lastName, + publicProfile: true + } + }); + + return saveResponse; + } catch (error) { + this.logger.error(`In Create User Repository------: ${JSON.stringify(error)}`); + throw error; + } + } + /** * * @param email @@ -112,7 +109,11 @@ export class UserRepository { try { return this.prisma.user.findFirst({ where: { - email + OR:[ + {email}, + {username: email} + ] + } }); } catch (error) { @@ -139,31 +140,24 @@ export class UserRepository { } } - /** + + /** * - * @param sessionId - * @returns Session details + * @param username + * @returns User details */ - async getSession(sessionId: string): Promise { - try { - return await this.prisma.session.findUnique({ - where: { - id: sessionId - } - }); - } catch (error) { - this.logger.error(`Not Found: ${JSON.stringify(error)}`); - throw new NotFoundException(error); + async getUserDetailsByUsername(username: string): Promise { + try { + return this.prisma.user.findFirst({ + where: { + username + } + }); + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } } - } - - async validateSession(sessionId: string): Promise { - const session = await this.prisma.session.findUnique({ - where: { id: sessionId }, - include: { user: true } - }); - return session; - } /** * @@ -178,6 +172,19 @@ export class UserRepository { return this.findUser(queryOptions); } + /** + * + * @param id + * @returns User profile data + */ + async getUserCredentialsById(credentialId: string): Promise { + return this.prisma.user_credentials.findUnique({ + where: { + credentialId + } + }); + } + /** * * @param id @@ -193,7 +200,7 @@ export class UserRepository { /** * - * @body updateUserProfile + * @Body updateUserProfile * @returns Update user profile data */ async updateUserProfile(updateUserProfile: UpdateUserProfile): Promise { @@ -465,6 +472,31 @@ export class UserRepository { } } + + /** + * + * @param userInfo + * @returns Updates user details + */ + // eslint-disable-next-line camelcase + async updateUserInfoByUserName(username: string, userInfo: IUserInformation): Promise { + try { + const updateUserDetails = await this.prisma.user.update({ + where: { + username + }, + data: { + firstName: userInfo.firstName, + lastName: userInfo.lastName + } + }); + return updateUserDetails; + } catch (error) { + this.logger.error(`Error in update isEmailVerified: ${error.message} `); + throw error; + } + } + /** * * @param queryOptions @@ -608,6 +640,20 @@ export class UserRepository { } } + async saveCertificateImageUrl(imageUrl: string, credentialId: string): Promise { + try { + const saveImageUrl = await this.prisma.user_credentials.create({ + data: { + imageUrl, + credentialId + } + }); + return saveImageUrl; + } catch (error) { + throw new Error(`Error saving certificate image URL: ${error.message}`); + } + } + async checkUniqueUserExist(email: string): Promise { try { return this.prisma.user.findUnique({ @@ -661,6 +707,31 @@ export class UserRepository { } } + + /** + * + * @param userInfo + * @returns Updates user credentials + */ + // eslint-disable-next-line camelcase + async addUserPasswordByUserName(username: string, userInfo: string): Promise { + try { + const updateUserDetails = await this.prisma.user.update({ + where: { + username + }, + data: { + password: userInfo + } + }); + return updateUserDetails; + } catch (error) { + this.logger.error(`Error in update isEmailVerified: ${error.message} `); + throw error; + } + } + + /** * * @param userId @@ -684,97 +755,6 @@ export class UserRepository { } } - async createSession(tokenDetails: ISession): Promise { - try { - const { sessionToken, userId, expires, refreshToken, accountId, sessionType, expiresAt } = tokenDetails; - const sessionResponse = await this.prisma.session.create({ - data: { - id: tokenDetails.id, - sessionToken, - expires, - userId, - refreshToken, - accountId, - sessionType, - expiresAt, - ...(tokenDetails.clientInfo ? { clientInfo: tokenDetails.clientInfo } : { clientInfo: { clientToken: true } }) - } - }); - return sessionResponse; - } catch (error) { - this.logger.error(`Error in creating session: ${error.message} `); - throw error; - } - } - - async fetchUserSessions(userId: string): Promise { - try { - const userSessionCount = await this.prisma.session.findMany({ - where: { - userId - }, - select: { - id: true, - userId: true, - expiresAt: true, - createdAt: true, - clientInfo: true, - sessionType: true - } - }); - return userSessionCount; - } catch (error) { - this.logger.error(`Error in getting user session details: ${error.message} `); - throw error; - } - } - - //this function is to fetch all session details for a user including token details without any restriction - async fetchUserSessionDetails(userId: string): Promise { - try { - const userSessionCount = await this.prisma.session.findMany({ - where: { - userId - } - }); - return userSessionCount; - } catch (error) { - this.logger.error(`Error in getting user session details: ${error.message} `); - throw error; - } - } - - async checkAccountDetails(userId: string): Promise { - try { - const accountDetails = await this.prisma.account.findUnique({ - where: { - userId - } - }); - return accountDetails; - } catch (error) { - this.logger.error(`Error in getting account details: ${error.message} `); - throw error; - } - } - - async addAccountDetails(accountDetails: ISession): Promise { - try { - const userAccountDetails = await this.prisma.account.create({ - data: { - userId: accountDetails.userId, - provider: ProviderType.KEYCLOAK, - providerAccountId: accountDetails.keycloakUserId, - tokenType: accountDetails.type - } - }); - return userAccountDetails; - } catch (error) { - this.logger.error(`Error in creating account: ${error.message}`); - throw error; - } - } - /** * * @param userId @@ -817,7 +797,7 @@ export class UserRepository { /** * - * @body updatePlatformSettings + * @Body updatePlatformSettings * @returns Update platform settings */ async updatePlatformSettings(updatePlatformSettings: PlatformSettings): Promise { @@ -843,6 +823,37 @@ export class UserRepository { } } + /** + * + * @Body updatePlatformSettings + * @returns Update ecosystem settings + */ + async updateEcosystemSettings(eosystemKeys: string[], ecosystemObj: object): Promise { + try { + for (const key of eosystemKeys) { + const ecosystemKey = await this.prisma.ecosystem_config.findFirst({ + where: { + key + } + }); + + await this.prisma.ecosystem_config.update({ + where: { + id: ecosystemKey.id + }, + data: { + value: ecosystemObj[key].toString() + } + }); + } + + return true; + } catch (error) { + this.logger.error(`error: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + async getPlatformSettings(): Promise { try { const getPlatformSettingsList = await this.prisma.platform_config.findMany(); @@ -853,14 +864,17 @@ export class UserRepository { } } - async updateOrgDeletedActivity( - orgId: string, - userId: string, - deletedBy: string, - recordType: RecordType, - userEmail: string, - txnMetadata: object - ): Promise { + async getEcosystemSettings(): Promise { + try { + const getEcosystemSettingsList = await this.prisma.ecosystem_config.findMany(); + return getEcosystemSettingsList; + } catch (error) { + this.logger.error(`error in getEcosystemSettings: ${JSON.stringify(error)}`); + throw new InternalServerErrorException(error); + } + } + + async updateOrgDeletedActivity(orgId: string, userId: string, deletedBy: string, recordType: RecordType, userEmail: string, txnMetadata: object): Promise { try { const orgDeletedActivity = await this.prisma.user_org_delete_activity.create({ data: { @@ -914,12 +928,10 @@ export class UserRepository { }); // Create a map for quick lookup of keycloakUserId, id, and email by email - const userMap = new Map( - users.map((user) => [user.email, { id: user.id, keycloakUserId: user.keycloakUserId, email: user.email }]) - ); + const userMap = new Map(users.map(user => [user.email, { id: user.id, keycloakUserId: user.keycloakUserId, email: user.email }])); // Collect the keycloakUserId, id, and email in the order of input emails - const result = userEmails.map((email) => { + const result = userEmails.map(email => { const user = userMap.get(email); return { id: user?.id || null, keycloakUserId: user?.keycloakUserId || null, email }; }); @@ -930,7 +942,7 @@ export class UserRepository { throw error; } } - + async storeUserRole(userId: string, userRoleId: string): Promise { try { const userRoleMapping = await this.prisma.user_role_mapping.create({ @@ -959,126 +971,4 @@ export class UserRepository { throw error; } } - - // eslint-disable-next-line camelcase - async handleGetUserOrganizations(userId: string): Promise { - try { - const getUserOrgs = await this.prisma.user_org_roles.findMany({ - where: { - userId - } - }); - - return getUserOrgs; - } catch (error) { - this.logger.error(`Error in handleGetUserOrganizations: ${error.message}`); - throw error; - } - } - - async destroySession(sessions: string[]): Promise { - try { - const userSessions = await this.prisma.session.deleteMany({ - where: { - id: { - in: sessions - } - } - }); - - return userSessions; - } catch (error) { - this.logger.error(`Error in logging out user: ${error.message}`); - throw error; - } - } - - async deleteSession(sessionId: string): Promise { - try { - const userSession = await this.prisma.session.delete({ - where: { - id: sessionId - } - }); - return userSession; - } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError && 'P2025' === error.code) { - this.logger.warn(`Session not found for deletion: ${sessionId}`); - throw new NotFoundException('Record to be deleted not found'); - } else { - this.logger.error(`Error in logging out user: ${error.message}`); - throw error; - } - } - } - - async deleteSessionBySessionId(sessionId: string, userId: string): Promise<{ message: string }> { - try { - await this.prisma.session.delete({ - where: { id: sessionId, userId } - }); - - return { message: 'Session deleted successfully' }; - } catch (error) { - if ('P2025' === error.code) { - throw new RpcException(new NotFoundException(`Session not found for userId: ${userId}`)); - } - this.logger.error(`Error in Deleting Session: ${error.message}`); - throw error; - } - } - - async fetchSessionByRefreshToken(refreshToken: string): Promise { - try { - const sessionDetails = await this.prisma.session.findFirst({ - where: { - refreshToken - } - }); - return sessionDetails; - } catch (error) { - this.logger.error(`Error in fetching session details::${error.message}`); - throw error; - } - } - - async deleteInactiveSessions(userId: string): Promise { - try { - const response = await this.prisma.session.deleteMany({ - where: { - expiresAt: { - lt: new Date() - }, - userId - } - }); - this.logger.debug('Deleted inactive sessions::', response); - return response; - } catch (error) { - this.logger.error(`Error in deleting the in active sessions::${error.message}`); - throw error; - } - } - - async updateSessionToken(id: string, tokenData: ITokenData): Promise { - if (!id || !tokenData) { - throw new BadRequestException(`Missing id or tokenData for session details update`); - } - try { - const sessionResponse = await this.prisma.session.update({ - where: { - id - }, - data: tokenData - }); - return sessionResponse; - } catch (error) { - this.logger.error(`Error in creating session: ${error.message} `); - if (error instanceof Prisma.PrismaClientKnownRequestError && 'P2025' === error.code) { - this.logger.warn(`Session not found for update: ${id}`); - throw new NotFoundException('Session not found'); - } - throw error; - } - } } diff --git a/apps/user/src/fido/fido.module.ts b/apps/user/src/fido/fido.module.ts index e3125189a..14077a4b3 100644 --- a/apps/user/src/fido/fido.module.ts +++ b/apps/user/src/fido/fido.module.ts @@ -20,7 +20,6 @@ import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from '../../repositories/user.repository'; import { UserService } from '../user.service'; import { AwsService } from '@credebl/aws'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -54,8 +53,7 @@ import { NATSClient } from '@credebl/common/NATSClient'; OrgRolesRepository, UserOrgRolesRepository, UserActivityService, - UserActivityRepository, - NATSClient + UserActivityRepository ] }) export class FidoModule { } diff --git a/apps/user/src/fido/fido.service.ts b/apps/user/src/fido/fido.service.ts index e5b3b90db..eb1087386 100644 --- a/apps/user/src/fido/fido.service.ts +++ b/apps/user/src/fido/fido.service.ts @@ -6,8 +6,6 @@ import { FidoUserRepository } from '../../repositories/fido-user.repository'; import { GenerateRegistrationDto, VerifyRegistrationPayloadDto, VerifyAuthenticationPayloadDto, UpdateFidoUserDetailsDto, credentialDto, updateDeviceDto } from './dtos/fido-user.dto'; import { UserDevicesRepository } from '../../repositories/user-device.repository'; import { PrismaService } from '@credebl/prisma-service'; -import { LoginUserDto } from 'apps/user/dtos/login-user.dto'; -import { UserService } from '../user.service'; @Injectable() export class FidoService { @@ -16,7 +14,6 @@ export class FidoService { private readonly fidoUserRepository: FidoUserRepository, private readonly userDevicesRepository: UserDevicesRepository, private readonly commonService: CommonService, - private readonly userService: UserService, private readonly prisma: PrismaService ) { } async generateRegistration(payload: GenerateRegistrationDto): Promise { @@ -150,16 +147,8 @@ export class FidoService { .then(async (response) => { if (true === response.verified) { await this.userDevicesRepository.updateFidoAuthCounter(credentialId, loginCounter); - const userDetails: LoginUserDto = { - email, - isPasskey: response.verified - }; - const authDetails = await this.userService.login(userDetails); - return authDetails; - } else { - throw new BadRequestException(ResponseMessages.fido.error.deviceNotFound); } - + return response; }); } } else { diff --git a/apps/user/src/main.ts b/apps/user/src/main.ts index 502c43950..8c3df1edc 100644 --- a/apps/user/src/main.ts +++ b/apps/user/src/main.ts @@ -5,7 +5,6 @@ import { UserModule } from './user.module'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -14,7 +13,7 @@ async function bootstrap(): Promise { transport: Transport.NATS, options: getNatsOptions(CommonConstants.USER_SERVICE, process.env.USER_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 5731998f1..79317d9da 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,61 +1,25 @@ -import { - ICheckUserDetails, - IOrgUsers, - IRestrictedUserSession, - ISessionDetails, - ISessions, - IUserDeletedActivity, - IUserForgotPassword, - IUserInformation, - IUserResetPassword, - IUserSignIn, - IUsersProfile, - Payload, - PlatformSettings, - UpdateUserProfile, - UserKeycloakId -} from '../interfaces/user.interface'; -import { - IResetPasswordResponse, - ISendVerificationEmail, - ISignInUser, - ISignUpUserResponse, - IUserInvitations, - IVerifyUserEmail -} from '@credebl/common/interfaces/user.interface'; -// eslint-disable-next-line camelcase -import { client_aliases, user, user_org_roles } from '@prisma/client'; - +import { IOrgUsers, Payload, ICheckUserDetails, PlatformSettings, IShareUserCertificate, UpdateUserProfile, IUsersProfile, IUserInformation, IUserSignIn, IUserCredentials, IUserResetPassword, IUserDeletedActivity, UserKeycloakId, IUserInformationUsernameBased, IUserNameSignIn} from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; -import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; import { Controller } from '@nestjs/common'; -import { IUsersActivity } from 'libs/user-activity/interface'; import { MessagePattern } from '@nestjs/microservices'; import { UserService } from './user.service'; import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; +import { user } from '@prisma/client'; +import { IUsersActivity } from 'libs/user-activity/interface'; +import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations, IResetPasswordResponse, ISignUpUserResponse } from '@credebl/common/interfaces/user.interface'; +import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; @Controller() export class UserController { - constructor(private readonly userService: UserService) {} - - /** - * Description: Fetch client aliases are its url - * @param email - * @returns Client alias and its url - */ - @MessagePattern({ cmd: 'get-client-alias-and-url' }) - // eslint-disable-next-line camelcase - async getClientAliases(): Promise { - return this.userService.getClientAliases(); - } + constructor(private readonly userService: UserService) { } /** * Description: Registers new user - * @param email + * @param email * @returns User's verification email sent status */ @MessagePattern({ cmd: 'send-verification-mail' }) - async sendVerificationMail(payload: { userEmailVerification: ISendVerificationEmail }): Promise { + async sendVerificationMail(payload: { userEmailVerification: ISendVerificationEmail }): Promise { return this.userService.sendVerificationMail(payload.userEmailVerification); } @@ -63,61 +27,54 @@ export class UserController { * Description: Verify user's email * @param email * @param verificationcode - * @returns User's email verification status + * @returns User's email verification status */ @MessagePattern({ cmd: 'user-email-verification' }) async verifyEmail(payload: { param: VerifyEmailTokenDto }): Promise { return this.userService.verifyEmail(payload.param); } - /** - * @body loginUserDto - * @returns User's access token details - */ + /** + * @Body loginUserDto + * @returns User's access token details + */ @MessagePattern({ cmd: 'user-holder-login' }) async login(payload: IUserSignIn): Promise { - const loginRes = await this.userService.login(payload); - return loginRes; + const loginRes = await this.userService.login(payload); + return loginRes; } - @MessagePattern({ cmd: 'fetch-session-details' }) - async getSession(payload: { sessionId: string }): Promise { - return this.userService.getSession(payload?.sessionId); - } - @MessagePattern({ cmd: 'check-session-details' }) - async checkSession(sessionId: string): Promise { - return this.userService.checkSession(sessionId); - } + /** + * @Body loginUserDto + * @returns User's access token details + */ - @MessagePattern({ cmd: 'refresh-token-details' }) - async refreshTokenDetails(refreshToken: string): Promise { - return this.userService.refreshTokenDetails(refreshToken); - } + @MessagePattern({ cmd: 'username-holder-login' }) + async usernameLogin(payload: IUserNameSignIn): Promise { + const loginRes = await this.userService.usernameLogin(payload); + return loginRes; + } - @MessagePattern({ cmd: 'session-details-by-userId' }) - async userSessions(userId: string): Promise { - return this.userService.userSessions(userId); - } - @MessagePattern({ cmd: 'delete-session-by-sessionId' }) - async deleteSession(payload: { sessionId: string; userId: string }): Promise<{ message: string }> { - return this.userService.deleteSession(payload.sessionId, payload.userId); + @MessagePattern({ cmd: 'refresh-token-details' }) + async refreshTokenDetails(refreshToken: string): Promise { + return this.userService.refreshTokenDetails(refreshToken); } @MessagePattern({ cmd: 'user-reset-password' }) async resetPassword(payload: IUserResetPassword): Promise { - return this.userService.resetPassword(payload); + return this.userService.resetPassword(payload); } @MessagePattern({ cmd: 'user-set-token-password' }) - async resetTokenPassword(payload: IUserResetPassword): Promise { + async resetTokenPassword(payload: IUserResetPassword): Promise { return this.userService.resetTokenPassword(payload); } @MessagePattern({ cmd: 'user-forgot-password' }) - async forgotPassword(payload: IUserForgotPassword): Promise { - return this.userService.forgotPassword(payload); + async forgotPassword(payload: IUserResetPassword): Promise { + return this.userService.forgotPassword(payload); } @MessagePattern({ cmd: 'get-user-profile' }) @@ -129,7 +86,7 @@ export class UserController { async getPublicProfile(payload: { username }): Promise { return this.userService.getPublicProfile(payload); } - /** + /** * @returns User details */ @MessagePattern({ cmd: 'update-user-profile' }) @@ -157,14 +114,23 @@ export class UserController { return this.userService.findUserByUserId(id); } + /** + * @param credentialId + * @returns User credentials + */ + @MessagePattern({ cmd: 'get-user-credentials-by-id' }) + async getUserCredentialsById(payload: { credentialId }): Promise { + return this.userService.getUserCredentialsById(payload); + } + /** * @returns Organization invitation data */ @MessagePattern({ cmd: 'get-org-invitations' }) - async invitations(payload: { id; status; pageNumber; pageSize; search }): Promise { - return this.userService.invitations(payload); + async invitations(payload: { id; status; pageNumber; pageSize; search; }): Promise { + return this.userService.invitations(payload); } - + /** * * @param payload @@ -178,85 +144,97 @@ export class UserController { return this.userService.acceptRejectInvitations(payload.acceptRejectInvitation, payload.userId); } + /** + * @param payload + * @returns User certificate URL + */ + @MessagePattern({ cmd: 'share-user-certificate' }) + async shareUserCertificate( + shareUserCredentials: IShareUserCertificate + ): Promise { + return this.userService.shareUserCertificate(shareUserCredentials); + } + /** * * @param payload * @returns organization users list */ @MessagePattern({ cmd: 'fetch-organization-user' }) - async getOrganizationUsers(payload: { orgId: string } & Payload): Promise { + async getOrganizationUsers(payload: {orgId:string} & Payload): Promise { return this.userService.getOrgUsers(payload.orgId, payload.pageNumber, payload.pageSize, payload.search); } /** - * @param payload - * @returns organization users list - */ + * @param payload + * @returns organization users list + */ @MessagePattern({ cmd: 'fetch-users' }) - async get(payload: { pageNumber: number; pageSize: number; search: string }): Promise { + async get(payload: { pageNumber: number, pageSize: number, search: string }): Promise { const users = this.userService.get(payload.pageNumber, payload.pageSize, payload.search); return users; } - - /** - * @param email - * @returns User's email exist status - * */ + + /** + * @param email + * @returns User's email exist status + * */ @MessagePattern({ cmd: 'check-user-exist' }) async checkUserExist(payload: { userEmail: string }): Promise { return this.userService.checkUserExist(payload.userEmail); } /** - * @body userInfo - * @returns User's registration status - */ + * @Body userInfo + * @returns User's registration status + */ @MessagePattern({ cmd: 'add-user' }) async addUserDetailsInKeyCloak(payload: { userInfo: IUserInformation }): Promise { return this.userService.createUserForToken(payload.userInfo); } + /** + * @Body userInfo + * @returns User's registration status + */ + @MessagePattern({ cmd: 'add-user-username-based' }) + async addUserDetailsUsernameBasedInKeyCloak(payload: { userInfo: IUserInformationUsernameBased }): Promise { + return this.userService.createUserForTokenUsernameBased(payload.userInfo); + } + // Fetch Users recent activities @MessagePattern({ cmd: 'get-user-activity' }) - async getUserActivity(payload: { userId: string; limit: number }): Promise { + async getUserActivity(payload: { userId: string, limit: number }): Promise { return this.userService.getUserActivity(payload.userId, payload.limit); } + // Delete user + @MessagePattern({ cmd: 'delete-user' }) + async deleteUser(userId: string): Promise { + return this.userService.deleteUser(userId); + } + @MessagePattern({ cmd: 'add-passkey' }) - async addPasskey(payload: { userEmail: string; userInfo: AddPasskeyDetailsDto }): Promise { + async addPasskey(payload: { userEmail: string, userInfo: AddPasskeyDetailsDto }): Promise { return this.userService.addPasskey(payload.userEmail, payload.userInfo); } - /** - * @returns platform settings updated status + /** + * @returns platform and ecosystem settings updated status */ @MessagePattern({ cmd: 'update-platform-settings' }) async updatePlatformSettings(payload: { platformSettings: PlatformSettings }): Promise { return this.userService.updatePlatformSettings(payload.platformSettings); } /** - * @returns platform settings + * @returns platform and ecosystem settings */ @MessagePattern({ cmd: 'fetch-platform-settings' }) - async getPlatformSettings(): Promise { - return this.userService.getPlatformSettings(); + async getPlatformEcosystemSettings(): Promise { + return this.userService.getPlatformEcosystemSettings(); } @MessagePattern({ cmd: 'org-deleted-activity' }) - async updateOrgDeletedActivity(payload: { - orgId; - userId; - deletedBy; - recordType; - userEmail; - txnMetadata; - }): Promise { - return this.userService.updateOrgDeletedActivity( - payload.orgId, - payload.userId, - payload.deletedBy, - payload.recordType, - payload.userEmail, - payload.txnMetadata - ); + async updateOrgDeletedActivity(payload: { orgId, userId, deletedBy, recordType, userEmail, txnMetadata }): Promise { + return this.userService.updateOrgDeletedActivity(payload.orgId, payload.userId, payload.deletedBy, payload.recordType, payload.userEmail, payload.txnMetadata); } @MessagePattern({ cmd: 'get-user-details-by-userId' }) @@ -271,18 +249,8 @@ export class UserController { } @MessagePattern({ cmd: 'get-user-info-by-user-email-keycloak' }) - async getUserByUserIdInKeycloak(payload: { email }): Promise { + async getUserByUserIdInKeycloak(payload: {email}): Promise { return this.userService.getUserByUserIdInKeycloak(payload.email); } - @MessagePattern({ cmd: 'get-user-organizations' }) - // eslint-disable-next-line camelcase - async getuserOrganizationByUserId(payload: { userId: string }): Promise { - return this.userService.getuserOrganizationByUserId(payload.userId); - } - - @MessagePattern({ cmd: 'user-logout' }) - async logout(logoutUserDto: ISessions): Promise { - return this.userService.logout(logoutUserDto); - } } diff --git a/apps/user/src/user.module.ts b/apps/user/src/user.module.ts index a8e262790..0bfd2b66b 100644 --- a/apps/user/src/user.module.ts +++ b/apps/user/src/user.module.ts @@ -20,11 +20,6 @@ import { UserDevicesRepository } from '../repositories/user-device.repository'; import { getNatsOptions } from '@credebl/common/nats.config'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -37,8 +32,6 @@ import { NATSClient } from '@credebl/common/NATSClient'; ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, FidoModule, OrgRolesModule ], @@ -58,8 +51,8 @@ import { NATSClient } from '@credebl/common/NATSClient'; UserOrgRolesRepository, UserActivityService, UserActivityRepository, - UserDevicesRepository, - NATSClient - ] + UserDevicesRepository + ], + exports: [ClientRegistrationService] }) export class UserModule {} diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 51b5a8a34..163b99dba 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -11,10 +11,11 @@ import { HttpException } from '@nestjs/common'; + import { ClientRegistrationService } from '@credebl/client-registration'; import { CommonService } from '@credebl/common'; import { EmailDto } from '@credebl/common/dtos/email.dto'; -import { LoginUserDto } from '../dtos/login-user.dto'; +import { LoginUserDto, LoginUserNameDto } from '../dtos/login-user.dto'; import { OrgRoles } from 'libs/org-roles/enums'; import { OrgRolesService } from '@credebl/org-roles'; import { PrismaService } from '@credebl/prisma-service'; @@ -24,52 +25,49 @@ import { URLUserEmailTemplate } from '../templates/user-email-template'; import { UserOrgRolesService } from '@credebl/user-org-roles'; import { UserRepository } from '../repositories/user.repository'; import { VerifyEmailTokenDto } from '../dtos/verify-email.dto'; -// eslint-disable-next-line camelcase -import { client_aliases, RecordType, session, user, user_org_roles } from '@prisma/client'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; +import { RecordType, user } from '@prisma/client'; import { + Attribute, ICheckUserDetails, OrgInvitations, PlatformSettings, + IShareUserCertificate, IOrgUsers, UpdateUserProfile, - IUserInformation, - IUsersProfile, - IUserResetPassword, - IUserDeletedActivity, - UserKeycloakId, - IEcosystemConfig, - IUserForgotPassword, - ISessionDetails, - ISessions, - IUpdateAccountDetails, - IRestrictedUserSession + IUserCredentials, + IUserInformation, + IUsersProfile, + IUserResetPassword, + IPuppeteerOption, + IShareDegreeCertificateRes, + IUserDeletedActivity, + UserKeycloakId, + IUserInformationUsernameBased } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; -import { Invitation, ProviderType, SessionType, TokenType, UserRole } from '@credebl/enum/enum'; +import { EcosystemConfigSettings, Invitation, UserCertificateId, UserRole } from '@credebl/enum/enum'; +import { WinnerTemplate } from '../templates/winner-template'; +import { ParticipantTemplate } from '../templates/participant-template'; +import { ArbiterTemplate } from '../templates/arbiter-template'; import validator from 'validator'; import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; +import puppeteer from 'puppeteer'; +import { WorldRecordTemplate } from '../templates/world-record-template'; import { IUsersActivity } from 'libs/user-activity/interface'; -import { - ISendVerificationEmail, - ISignInUser, - IVerifyUserEmail, - IUserInvitations, - IResetPasswordResponse, - ISignUpUserResponse, - IVerificationEmail -} from '@credebl/common/interfaces/user.interface'; +import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations, IResetPasswordResponse, ISignUpUserResponse } from '@credebl/common/interfaces/user.interface'; import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto'; import { URLUserResetPasswordTemplate } from '../templates/reset-password-template'; import { toNumber } from '@credebl/common/cast.helper'; import * as jwt from 'jsonwebtoken'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { getCredentialsByAlias } from 'apps/api-gateway/src/user/utils'; -import { EmailService } from '@credebl/common/email.service'; +import { EventPinnacle } from '../templates/event-pinnacle'; +import { EventCertificate } from '../templates/event-certificates'; +import * as QRCode from 'qrcode'; @Injectable() export class UserService { @@ -85,27 +83,9 @@ export class UserService { private readonly awsService: AwsService, private readonly userDevicesRepository: UserDevicesRepository, private readonly logger: Logger, - @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy, - private readonly natsClient: NATSClient, - private readonly emailService: EmailService + @Inject('NATS_CLIENT') private readonly userServiceProxy: ClientProxy ) {} - /** - * - * @returns client alias and its url - */ - - // eslint-disable-next-line camelcase - async getClientAliases(): Promise { - try { - const clientAliases = await this.userRepository.fetchClientAliases(); - return clientAliases; - } catch (error) { - this.logger.error(`In Create User : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - /** * * @param userEmailVerification @@ -114,8 +94,8 @@ export class UserService { async sendVerificationMail(userEmailVerification: ISendVerificationEmail): Promise { try { - const { email, brandLogoUrl, platformName, clientAlias } = userEmailVerification; - + const { email, brandLogoUrl, platformName, clientId, clientSecret } = userEmailVerification; + if ('PROD' === process.env.PLATFORM_PROFILE_MODE) { // eslint-disable-next-line prefer-destructuring const domain = email.split('@')[1]; @@ -123,9 +103,9 @@ export class UserService { throw new BadRequestException(ResponseMessages.user.error.InvalidEmailDomain); } } - + const userDetails = await this.userRepository.checkUserExist(email); - + if (userDetails) { if (userDetails.isEmailVerified) { throw new ConflictException(ResponseMessages.user.error.exists); @@ -133,47 +113,33 @@ export class UserService { throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } } - + const verifyCode = uuidv4(); let sendVerificationMail: boolean; - const clientDetails = await getCredentialsByAlias(clientAlias); - try { - const token = await this.clientRegistrationService.getManagementToken( - clientDetails.clientId, - clientDetails.clientSecret - ); - const getClientData = await this.clientRegistrationService.getClientRedirectUrl(clientDetails.clientId, token); - const [redirectUrl] = getClientData[0]?.redirectUris || []; + const token = await this.clientRegistrationService.getManagementToken(clientId, clientSecret); + const getClientData = await this.clientRegistrationService.getClientRedirectUrl(clientId, token); + const [redirectUrl] = getClientData[0]?.redirectUris || []; + if (!redirectUrl) { throw new NotFoundException(ResponseMessages.user.error.redirectUrlNotFound); } - - sendVerificationMail = await this.sendEmailForVerification({ - email, - verificationCode: verifyCode, - redirectUrl, - clientId: clientDetails.clientId, - brandLogoUrl, - platformName, - redirectTo: clientDetails.domain, - clientAlias - }); + sendVerificationMail = await this.sendEmailForVerification(email, verifyCode, redirectUrl, clientId, brandLogoUrl, platformName); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } - + if (sendVerificationMail) { const uniqueUsername = await this.createUsername(email, verifyCode); userEmailVerification.username = uniqueUsername; - userEmailVerification.clientId = clientDetails.clientId; - userEmailVerification.clientSecret = clientDetails.clientSecret; + userEmailVerification.clientId = clientId; + userEmailVerification.clientSecret = clientSecret; const resUser = await this.userRepository.createUser(userEmailVerification, verifyCode); return resUser; - } + } } catch (error) { this.logger.error(`In Create User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -210,12 +176,11 @@ export class UserService { * @returns */ - async sendEmailForVerification(verificationEmailParameter: IVerificationEmail): Promise { + async sendEmailForVerification(email: string, verificationCode: string, redirectUrl: string, clientId: string, brandLogoUrl:string, platformName: string): Promise { try { - const { email, verificationCode, brandLogoUrl, platformName, redirectTo, clientAlias } = - verificationEmailParameter; const platformConfigData = await this.prisma.platform_config.findMany(); + const decryptClientId = await this.commonService.decryptPassword(clientId); const urlEmailTemplate = new URLUserEmailTemplate(); const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; @@ -223,15 +188,8 @@ export class UserService { const platform = platformName || process.env.PLATFORM_NAME; emailData.emailSubject = `[${platform}] Verify your email to activate your account`; - emailData.emailHtml = urlEmailTemplate.getUserURLTemplate( - email, - verificationCode, - brandLogoUrl, - platformName, - redirectTo, - clientAlias - ); - const isEmailSent = await this.emailService.sendEmail(emailData); + emailData.emailHtml = await urlEmailTemplate.getUserURLTemplate(email, verificationCode, redirectUrl, decryptClientId, brandLogoUrl, platformName); + const isEmailSent = await sendEmail(emailData); if (isEmailSent) { return isEmailSent; } else { @@ -302,12 +260,9 @@ export class UserService { if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.adduser); } - let keycloakDetails = null; - - const token = await this.clientRegistrationService.getManagementToken( - checkUserDetails.clientId, - checkUserDetails.clientSecret - ); + let keycloakDetails = null; + + const token = await this.clientRegistrationService.getManagementToken(checkUserDetails.clientId, checkUserDetails.clientSecret); if (userInfo.isPasskey) { const resUser = await this.userRepository.addUserPassword(email.toLowerCase(), userInfo.password); const userDetails = await this.userRepository.getUserDetails(email.toLowerCase()); @@ -318,12 +273,8 @@ export class UserService { } userInfo.password = decryptedPassword; - try { - keycloakDetails = await this.clientRegistrationService.createUser( - userInfo, - process.env.KEYCLOAK_REALM, - token - ); + try { + keycloakDetails = await this.clientRegistrationService.createUser(userInfo, process.env.KEYCLOAK_REALM, token); } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } @@ -332,18 +283,16 @@ export class UserService { userInfo.password = decryptedPassword; - try { - keycloakDetails = await this.clientRegistrationService.createUser( - userInfo, - process.env.KEYCLOAK_REALM, - token - ); + try { + keycloakDetails = await this.clientRegistrationService.createUser(userInfo, process.env.KEYCLOAK_REALM, token); } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } } - await this.userRepository.updateUserDetails(userDetails.id, keycloakDetails.keycloakUserId.toString()); + await this.userRepository.updateUserDetails(userDetails.id, + keycloakDetails.keycloakUserId.toString() + ); if (userInfo?.isHolder) { const getUserRole = await this.userRepository.getUserRole(UserRole.HOLDER); @@ -355,9 +304,9 @@ export class UserService { } const realmRoles = await this.clientRegistrationService.getAllRealmRoles(token); - - const holderRole = realmRoles.filter((role) => role.name === OrgRoles.HOLDER); - const holderRoleData = 0 < holderRole.length && holderRole[0]; + + const holderRole = realmRoles.filter(role => role.name === OrgRoles.HOLDER); + const holderRoleData = 0 < holderRole.length && holderRole[0]; const payload = [ { @@ -366,22 +315,9 @@ export class UserService { } ]; - await this.clientRegistrationService.createUserHolderRole( - token, - keycloakDetails.keycloakUserId.toString(), - payload - ); + await this.clientRegistrationService.createUserHolderRole(token, keycloakDetails.keycloakUserId.toString(), payload); const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderOrgRole.id, null, holderRoleData.id); - const userAccountDetails = { - userId: userDetails?.id, - provider: ProviderType.KEYCLOAK, - keycloakUserId: keycloakDetails.keycloakUserId, - // eslint-disable-next-line camelcase - type: TokenType.BEARER_TOKEN - }; - - await this.userRepository.addAccountDetails(userAccountDetails); return { userId: userDetails?.id }; } catch (error) { @@ -390,6 +326,92 @@ export class UserService { } } + + async createUserForTokenUsernameBased(userInfo: IUserInformationUsernameBased): Promise { + try { + + const checkUserDetails = await this.userRepository.getUserDetailsByUsername(userInfo.username); + + if (checkUserDetails) { + throw new ConflictException(ResponseMessages.user.error.exists); + } + + const resUser = await this.userRepository.createUserWithoutVerification(userInfo); + let keycloakDetails = null; + + const token = await this.clientRegistrationService.getManagementToken(userInfo.clientId, userInfo.clientSecret); + if (userInfo.isPasskey) { + const resUser = await this.userRepository.addUserPasswordByUserName(userInfo.username, userInfo.password); + const userDetails = await this.userRepository.getUserDetailsByUsername(userInfo.username); + const decryptedPassword = await this.commonService.decryptPassword(userDetails.password); + + if (!resUser) { + throw new NotFoundException(ResponseMessages.user.error.invalidUsername); + } + + userInfo.password = decryptedPassword; + try { + keycloakDetails = await this.clientRegistrationService.createUserUserNameBased(userInfo, process.env.KEYCLOAK_REALM, token); + } catch (error) { + throw new InternalServerErrorException('Error while registering user on keycloak'); + } + } else { + const decryptedPassword = await this.commonService.decryptPassword(userInfo.password); + + userInfo.password = decryptedPassword; + + try { + keycloakDetails = await this.clientRegistrationService.createUserUserNameBased(userInfo, process.env.KEYCLOAK_REALM, token); + } catch (error) { + throw new InternalServerErrorException('Error while registering user on keycloak'); + } + } + + await this.userRepository.updateUserDetails(resUser.id, + keycloakDetails.keycloakUserId.toString() + ); + + if (userInfo?.isHolder) { + const getUserRole = await this.userRepository.getUserRole(UserRole.HOLDER); + + if (!getUserRole) { + throw new NotFoundException(ResponseMessages.user.error.userRoleNotFound); + } + await this.userRepository.storeUserRole(resUser.id, getUserRole?.id); + } + + const realmRoles = await this.clientRegistrationService.getAllRealmRoles(token); + + const holderRole = realmRoles.filter(role => role.name === OrgRoles.HOLDER); + const holderRoleData = 0 < holderRole.length && holderRole[0]; + + const payload = [ + { + id: holderRoleData.id, + name: holderRoleData.name + } + ]; + + await this.clientRegistrationService.createUserHolderRole(token, keycloakDetails.keycloakUserId.toString(), payload); + const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); + await this.userOrgRoleService.createUserOrgRole(resUser.id, holderOrgRole.id, null, holderRoleData.id); + + return { userId: resUser?.id }; + } catch (error) { + this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + + async deleteUser(userId:string): Promise { + + const res = await this.userRepository.deleteUser(userId); + + const token = await this.clientRegistrationService.getManagementToken(res.clientId, res.clientSecret); + return this.clientRegistrationService.deleteUser(res.keycloakUserId, process.env.KEYCLOAK_REALM, token); + + } + async addPasskey(email: string, userInfo: AddPasskeyDetailsDto): Promise { try { if (!email.toLowerCase()) { @@ -437,14 +459,16 @@ export class UserService { * @returns User access token details */ async login(loginUserDto: LoginUserDto): Promise { - const { email, password, isPasskey, clientInfo } = loginUserDto; + const { email, password, isPasskey } = loginUserDto; try { + this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } + if (userData && !userData.isEmailVerified) { throw new BadRequestException(ResponseMessages.user.error.verifyMail); } @@ -453,155 +477,66 @@ export class UserService { throw new UnauthorizedException(ResponseMessages.user.error.registerFido); } - this.userRepository.deleteInactiveSessions(userData?.id); - const userSessionDetails = await this.userRepository.fetchUserSessions(userData?.id); - if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { - throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); - } - let tokenDetails; if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { const getUserDetails = await this.userRepository.getUserDetails(userData.email.toLowerCase()); const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); - tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } else { - const decryptedPassword = await this.commonService.decryptPassword(password); - tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); - } - const decodedToken = jwt.decode(tokenDetails?.refresh_token); - if (!decodedToken || 'object' !== typeof decodedToken || !decodedToken.exp || !decodedToken.sid) { - throw new UnauthorizedException(ResponseMessages.user.error.refreshTokenExpired); - } - const expiresAt = new Date(decodedToken.exp * 1000); - - const sessionData = { - id: decodedToken.sid, - sessionToken: tokenDetails?.access_token, - userId: userData?.id, - expires: tokenDetails?.expires_in, - refreshToken: tokenDetails?.refresh_token, - sessionType: SessionType.USER_SESSION, - expiresAt, - clientInfo - }; - - const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); - let addSessionDetails; - let accountData; - if (null === fetchAccountDetails) { - accountData = { - userId: userData?.id, - keycloakUserId: userData?.keycloakUserId, - type: TokenType.BEARER_TOKEN - }; - await this.userRepository.addAccountDetails(accountData).then(async (response) => { - const finalSessionData = { ...sessionData, accountId: response.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - }); - } else { - const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); + const decryptedPassword = await this.commonService.decryptPassword(password); + return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } - - const finalResponse = { - ...tokenDetails, - sessionId: addSessionDetails.id - }; - - return finalResponse; } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async getSession(sessionId: string): Promise { - try { - const onceDecoded = decodeURIComponent(sessionId); - const decodedSessionId = decodeURIComponent(onceDecoded); - const decryptedSessionId = await this.commonService.decryptPassword(decodedSessionId); - const sessionDetails = await this.userRepository.getSession(decryptedSessionId); - return sessionDetails; - } catch (error) { - this.logger.error(`In fetching session details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } + async usernameLogin(loginUserDto: LoginUserNameDto): Promise { + const { username, password, isPasskey } = loginUserDto; - async checkSession(sessionId: string): Promise { try { - const sessionDetails = await this.userRepository.getSession(sessionId); - return sessionDetails; - } catch (error) { - this.logger.error(`In fetching session details : ${JSON.stringify(error)}`); - throw new RpcException(error.response ? error.response : error); - } - } - async refreshTokenDetails(refreshToken: string): Promise { - try { - const data = jwt.decode(refreshToken) as jwt.JwtPayload; - const refreshTokenExp = new Date(data.exp * 1000); - const currentTime = new Date(); - if (refreshTokenExp < currentTime) { - await this.userRepository.deleteSession(data?.sid); - throw new UnauthorizedException(ResponseMessages.user.error.refreshTokenExpired); - } - const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); - const tokenResponse = await this.clientRegistrationService.getAccessToken( - refreshToken, - userByKeycloakId?.['clientId'], - userByKeycloakId?.['clientSecret'] - ); - // Fetch the details from account table based on userid and refresh token - const userAccountDetails = await this.userRepository.checkAccountDetails(userByKeycloakId?.['id']); - // Update the account details with latest access token, refresh token and exp date - if (!userAccountDetails) { - throw new NotFoundException(ResponseMessages.user.error.userAccountNotFound); + const userData = await this.userRepository.getUserDetailsByUsername(username); + if (!userData) { + throw new NotFoundException(ResponseMessages.user.error.notFound); } - // Fetch session details - const sessionDetails = await this.userRepository.getSession(data.sid); - if (!sessionDetails) { - throw new NotFoundException(ResponseMessages.user.error.userSessionNotFound); + if (true === isPasskey && false === userData?.isFidoVerified) { + throw new UnauthorizedException(ResponseMessages.user.error.registerFido); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const decodedToken: any = jwt.decode(tokenResponse?.refresh_token); - const expiresAt = new Date(decodedToken.exp * 1000); - const sessionData = { - sessionToken: tokenResponse.access_token, - expires: tokenResponse.expires_in, - refreshToken: tokenResponse.refresh_token, - expiresAt - }; - const addSessionDetails = await this.userRepository.updateSessionToken(tokenResponse.session_state, sessionData); - if (!addSessionDetails) { - throw new InternalServerErrorException(ResponseMessages.user.error.errorInSessionCreation); - } + if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { + const getUserDetails = await this.userRepository.getUserDetailsByUsername(username); + const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); + return await this.generateToken(username, decryptedPassword, userData); + } else { - return tokenResponse; + const decryptedPassword = await this.commonService.decryptPassword(password); + return await this.generateToken(username, decryptedPassword, userData); + } } catch (error) { - this.logger.error(`In refreshTokenDetails : ${JSON.stringify(error)}`); + this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async userSessions(userId: string): Promise { + async refreshTokenDetails(refreshToken: string): Promise { + try { - return await this.userRepository.fetchUserSessions(userId); + try { + const data = jwt.decode(refreshToken) as jwt.JwtPayload; + const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); + const tokenResponse = await this.clientRegistrationService.getAccessToken(refreshToken, userByKeycloakId?.['clientId'], userByKeycloakId?.['clientSecret']); + return tokenResponse; + } catch (error) { + throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken); + } + } catch (error) { - this.logger.error(`get user sessions: ${JSON.stringify(error)}`); + this.logger.error(`In refreshTokenDetails : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); - } - } - async deleteSession(sessionId: string, userId: string): Promise<{ message: string }> { - try { - return await this.userRepository.deleteSessionBySessionId(sessionId, userId); - } catch (error) { - this.logger.error(`delete session by session id: ${JSON.stringify(error)}`); - throw error; } } @@ -614,11 +549,12 @@ export class UserService { /** * Forgot password - * @param forgotPasswordDto - * @returns + * @param forgotPasswordDto + * @returns */ - async forgotPassword(forgotPasswordDto: IUserForgotPassword): Promise { - const { email, brandLogoUrl, platformName, endpoint, clientAlias } = forgotPasswordDto; + async forgotPassword(forgotPasswordDto: IUserResetPassword): Promise { + const { email } = forgotPasswordDto; + try { this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); @@ -633,7 +569,7 @@ export class UserService { const token = uuidv4(); const expirationTime = new Date(); expirationTime.setHours(expirationTime.getHours() + 1); // Set expiration time to 1 hour from now - + const tokenCreated = await this.userRepository.createTokenForResetPassword(userData.id, token, expirationTime); if (!tokenCreated) { @@ -641,14 +577,7 @@ export class UserService { } try { - await this.sendEmailForResetPassword( - email, - brandLogoUrl, - platformName, - endpoint, - tokenCreated.token, - clientAlias - ); + await this.sendEmailForResetPassword(email, tokenCreated.token); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -657,6 +586,7 @@ export class UserService { id: tokenCreated.id, email: userData.email }; + } catch (error) { this.logger.error(`Error In forgotPassword : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -665,18 +595,11 @@ export class UserService { /** * Send email for token verification of reset password - * @param email - * @param verificationCode - * @returns + * @param email + * @param verificationCode + * @returns */ - async sendEmailForResetPassword( - email: string, - brandLogoUrl: string, - platformName: string, - endpoint: string, - verificationCode: string, - clientAlias: string | undefined - ): Promise { + async sendEmailForResetPassword(email: string, verificationCode: string): Promise { try { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -684,19 +607,10 @@ export class UserService { const emailData = new EmailDto(); emailData.emailFrom = platformConfigData[0].emailFrom; emailData.emailTo = email; + emailData.emailSubject = `[${process.env.PLATFORM_NAME}] Important: Password Reset Request`; - const platform = platformName || process.env.PLATFORM_NAME; - emailData.emailSubject = `[${platform}] Important: Password Reset Request`; - - emailData.emailHtml = urlEmailTemplate.getUserResetPasswordTemplate( - email, - platform, - brandLogoUrl, - endpoint, - verificationCode, - clientAlias - ); - const isEmailSent = await this.emailService.sendEmail(emailData); + emailData.emailHtml = await urlEmailTemplate.getUserResetPasswordTemplate(email, verificationCode); + const isEmailSent = await sendEmail(emailData); if (isEmailSent) { return isEmailSent; } else { @@ -710,10 +624,11 @@ export class UserService { /** * Create reset password token - * @param resetPasswordDto + * @param resetPasswordDto * @returns user details */ async resetTokenPassword(resetPasswordDto: IUserResetPassword): Promise { + const { email, password, token } = resetPasswordDto; try { @@ -726,32 +641,30 @@ export class UserService { if (userData && !userData.isEmailVerified) { throw new BadRequestException(ResponseMessages.user.error.verifyMail); } - + const tokenDetails = await this.userRepository.getResetPasswordTokenDetails(userData.id, token); - if (!tokenDetails || new Date() > tokenDetails.expiresAt) { + if (!tokenDetails || (new Date() > tokenDetails.expiresAt)) { throw new BadRequestException(ResponseMessages.user.error.invalidResetLink); } const decryptedPassword = await this.commonService.decryptPassword(password); - try { - const authToken = await this.clientRegistrationService.getManagementToken( - userData.clientId, - userData.clientSecret - ); + try { + + + const authToken = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret); userData.password = decryptedPassword; if (userData.keycloakUserId) { await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, authToken); - } else { - const keycloakDetails = await this.clientRegistrationService.createUser( - userData, - process.env.KEYCLOAK_REALM, - authToken + } else { + const keycloakDetails = await this.clientRegistrationService.createUser(userData, process.env.KEYCLOAK_REALM, authToken); + await this.userRepository.updateUserDetails(userData.id, + keycloakDetails.keycloakUserId.toString() ); - await this.userRepository.updateUserDetails(userData.id, keycloakDetails.keycloakUserId.toString()); } await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, password); + } catch (error) { this.logger.error(`Error reseting the password`, error); throw new InternalServerErrorException('Error while reseting user password'); @@ -763,6 +676,7 @@ export class UserService { id: userData.id, email: userData.email }; + } catch (error) { this.logger.error(`Error In resetTokenPassword : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -771,6 +685,7 @@ export class UserService { findUserByUserId(id: string): Promise { return this.userRepository.getUserById(id); + } async resetPassword(resetPasswordDto: IUserResetPassword): Promise { @@ -795,26 +710,23 @@ export class UserService { } const tokenResponse = await this.generateToken(email.toLowerCase(), oldDecryptedPassword, userData); - + if (tokenResponse) { userData.password = newDecryptedPassword; - try { - let keycloakDetails = null; - const token = await this.clientRegistrationService.getManagementToken( - userData.clientId, - userData.clientSecret - ); + try { + let keycloakDetails = null; + const token = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret); if (userData.keycloakUserId) { - await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, token); + + keycloakDetails = await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, token); await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, newPassword); + } else { - keycloakDetails = await this.clientRegistrationService.createUser( - userData, - process.env.KEYCLOAK_REALM, - token + keycloakDetails = await this.clientRegistrationService.createUser(userData, process.env.KEYCLOAK_REALM, token); + await this.userRepository.updateUserDetails(userData.id, + keycloakDetails.keycloakUserId.toString() ); - await this.userRepository.updateUserDetails(userData.id, keycloakDetails.keycloakUserId.toString()); await this.updateFidoVerifiedUser(email.toLowerCase(), userData.isFidoVerified, newPassword); } @@ -822,12 +734,14 @@ export class UserService { id: userData.id, email: userData.email }; + } catch (error) { throw new InternalServerErrorException('Error while registering user on keycloak'); } } else { throw new BadRequestException(ResponseMessages.user.error.invalidCredentials); } + } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -835,57 +749,57 @@ export class UserService { } async generateToken(email: string, password: string, userData: user): Promise { - if (userData.keycloakUserId) { - try { - const tokenResponse = await this.clientRegistrationService.getUserToken( - email, - password, - userData.clientId, - userData.clientSecret - ); - tokenResponse.isRegisteredToSupabase = false; - return tokenResponse; - } catch (error) { - throw new UnauthorizedException(ResponseMessages.user.error.invalidCredentials); - } - } else { - const supaInstance = await this.supabaseService.getClient(); - const { data, error } = await supaInstance.auth.signInWithPassword({ - email, - password - }); - - this.logger.error(`Supa Login Error::`, JSON.stringify(error)); - if (error) { - throw new BadRequestException(error?.message); - } + if (userData.keycloakUserId) { - const token = data?.session; + try { + const tokenResponse = await this.clientRegistrationService.getUserToken(email, password, userData.clientId, userData.clientSecret); + tokenResponse.isRegisteredToSupabase = false; + return tokenResponse; + } catch (error) { + throw new UnauthorizedException(ResponseMessages.user.error.invalidCredentials); + } + + } else { + const supaInstance = await this.supabaseService.getClient(); + const { data, error } = await supaInstance.auth.signInWithPassword({ + email, + password + }); + + this.logger.error(`Supa Login Error::`, JSON.stringify(error)); + + if (error) { + throw new BadRequestException(error?.message); + } + + const token = data?.session; - return { - // eslint-disable-next-line camelcase - access_token: token.access_token, - // eslint-disable-next-line camelcase - token_type: token.token_type, - // eslint-disable-next-line camelcase - expires_in: token.expires_in, - // eslint-disable-next-line camelcase - expires_at: token.expires_at, - isRegisteredToSupabase: true - }; - } + return { + // eslint-disable-next-line camelcase + access_token: token.access_token, + // eslint-disable-next-line camelcase + token_type: token.token_type, + // eslint-disable-next-line camelcase + expires_in: token.expires_in, + // eslint-disable-next-line camelcase + expires_at: token.expires_at, + isRegisteredToSupabase: true + }; + } } async getProfile(payload: { id }): Promise { try { const userData = await this.userRepository.getUserById(payload.id); - - if ('true' === process.env.IS_ECOSYSTEM_ENABLE) { - const ecosystemSettings = await this._getEcosystemConfig(); - for (const setting of ecosystemSettings) { - userData[setting.key] = 'true' === setting.value; + const ecosystemSettingsList = await this.prisma.ecosystem_config.findMany({ + where: { + OR: [{ key: EcosystemConfigSettings.ENABLE_ECOSYSTEM }, { key: EcosystemConfigSettings.MULTI_ECOSYSTEM }] } + }); + + for (const setting of ecosystemSettingsList) { + userData[setting.key] = 'true' === setting.value; } return userData; @@ -895,27 +809,6 @@ export class UserService { } } - async _getEcosystemConfig(): Promise { - const pattern = { cmd: 'get-ecosystem-config-details' }; - const payload = {}; - - const getEcosystemConfigDetails = await this.userServiceProxy - .send(pattern, payload) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); - - return getEcosystemConfigDetails; - } - async getPublicProfile(payload: { username }): Promise { try { const userProfile = await this.userRepository.getUserPublicProfile(payload.username); @@ -931,6 +824,19 @@ export class UserService { } } + async getUserCredentialsById(payload: { credentialId }): Promise { + try { + const userCredentials = await this.userRepository.getUserCredentialsById(payload.credentialId); + if (!userCredentials) { + throw new NotFoundException(ResponseMessages.user.error.credentialNotFound); + } + return userCredentials; + } catch (error) { + this.logger.error(`get user: ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + async updateUserProfile(updateUserProfileDto: UpdateUserProfile): Promise { try { return this.userRepository.updateUserProfile(updateUserProfileDto); @@ -989,12 +895,13 @@ export class UserService { payload.pageNumber, payload.pageSize, payload.search - ); - - const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); - invitationsData['invitations'] = invitations; + ); + + const invitations: OrgInvitations[] = await this.updateOrgInvitations(invitationsData['invitations']); + invitationsData['invitations'] = invitations; return invitationsData; + } catch (error) { this.logger.error(`Error in get invitations: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1017,9 +924,9 @@ export class UserService { search }; - const invitationsData = await this.natsClient - .send(this.userServiceProxy, pattern, payload) - + const invitationsData = await this.userServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1035,6 +942,8 @@ export class UserService { } async updateOrgInvitations(invitations: OrgInvitations[]): Promise { + + const updatedInvitations = []; for (const invitation of invitations) { @@ -1061,20 +970,17 @@ export class UserService { * @param userId * @returns Organization invitation status */ - async acceptRejectInvitations( - acceptRejectInvitation: AcceptRejectInvitationDto, - userId: string - ): Promise { + async acceptRejectInvitations(acceptRejectInvitation: AcceptRejectInvitationDto, userId: string): Promise { try { const userData = await this.userRepository.getUserById(userId); - + if (Invitation.ACCEPTED === acceptRejectInvitation.status) { - const payload = { userId }; + const payload = {userId}; const TotalOrgs = await this._getTotalOrgCount(payload); - + if (TotalOrgs >= toNumber(`${process.env.MAX_ORG_LIMIT}`)) { - throw new BadRequestException(ResponseMessages.user.error.userOrgsLimit); - } + throw new BadRequestException(ResponseMessages.user.error.userOrgsLimit); + } } return this.fetchInvitationsStatus(acceptRejectInvitation, userData.keycloakUserId, userData.email, userId); } catch (error) { @@ -1083,12 +989,12 @@ export class UserService { } } - async _getTotalOrgCount(payload): Promise { + async _getTotalOrgCount(payload): Promise { const pattern = { cmd: 'get-organizations-count' }; - const getOrganizationCount = await this.natsClient - .send(this.userServiceProxy, pattern, payload) - + const getOrganizationCount = await this.userServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1103,6 +1009,123 @@ export class UserService { return getOrganizationCount; } + async shareUserCertificate(shareUserCertificate: IShareUserCertificate): Promise { + + let template; + const attributeArray = []; + let attributeJson = {}; + const attributePromises = shareUserCertificate.attributes.map(async (iterator: Attribute) => { + attributeJson = { + [iterator.name]: iterator.value + }; + attributeArray.push(attributeJson); + }); + await Promise.all(attributePromises); + switch (shareUserCertificate.schemaId.split(':')[2]) { + case UserCertificateId.WINNER: + // eslint-disable-next-line no-case-declarations + const userWinnerTemplate = new WinnerTemplate(); + template = await userWinnerTemplate.getWinnerTemplate(attributeArray); + break; + case UserCertificateId.PARTICIPANT: + // eslint-disable-next-line no-case-declarations + const userParticipantTemplate = new ParticipantTemplate(); + template = await userParticipantTemplate.getParticipantTemplate(attributeArray); + break; + case UserCertificateId.ARBITER: + // eslint-disable-next-line no-case-declarations + const userArbiterTemplate = new ArbiterTemplate(); + template = await userArbiterTemplate.getArbiterTemplate(attributeArray); + break; + case UserCertificateId.WORLD_RECORD: + // eslint-disable-next-line no-case-declarations + const userWorldRecordTemplate = new WorldRecordTemplate(); + template = await userWorldRecordTemplate.getWorldRecordTemplate(attributeArray); + break; + case UserCertificateId.AYANWORKS_EVENT: + // eslint-disable-next-line no-case-declarations + const QRDetails = await this.getShorteningURL(shareUserCertificate, attributeArray); + + if (shareUserCertificate.attributes.some(item => item.value.toLocaleLowerCase().includes('pinnacle'))) { + const userPinnacleTemplate = new EventPinnacle(); + template = await userPinnacleTemplate.getPinnacleWinner(attributeArray, QRDetails); + } else { + const userCertificateTemplate = new EventCertificate(); + template = await userCertificateTemplate.getCertificateWinner(attributeArray, QRDetails); + } + break; + default: + throw new NotFoundException('error in get attributes'); + } + + //Need to handle the option for all type of certificate + const option: IPuppeteerOption = {height: 974, width: 1606}; + + const imageBuffer = + await this.convertHtmlToImage(template, shareUserCertificate.credentialId, option); + + const imageUrl = await this.awsService.uploadUserCertificate( + imageBuffer, + 'svg', + 'certificates', + process.env.AWS_PUBLIC_BUCKET_NAME, + 'base64', + 'certificates' + ); + const existCredentialId = await this.userRepository.getUserCredentialsById(shareUserCertificate.credentialId); + + if (existCredentialId) { + return `${process.env.FRONT_END_URL}/certificates/${shareUserCertificate.credentialId}`; + } + + const saveCredentialData = await this.saveCertificateUrl(imageUrl, shareUserCertificate.credentialId); + + if (!saveCredentialData) { + throw new BadRequestException(ResponseMessages.schema.error.notStoredCredential); + } + + return `${process.env.FRONT_END_URL}/certificates/${shareUserCertificate.credentialId}`; + + } + + async saveCertificateUrl(imageUrl: string, credentialId: string): Promise { + return this.userRepository.saveCertificateImageUrl(imageUrl, credentialId); + } + + async convertHtmlToImage(template: string, credentialId: string, option?: IPuppeteerOption): Promise { + const browser = await puppeteer.launch({ + executablePath: '/usr/bin/google-chrome', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + protocolTimeout: 800000, //initial - 200000 + headless: true + }); + + const options: IPuppeteerOption = (option && 0 < Object.keys(option).length) ? option : {width: 0, height: 1000}; + + const page = await browser.newPage(); + await page.setViewport({ width: options?.width, height: options?.height, deviceScaleFactor: 2}); + await page.setContent(template); + const screenshot = await page.screenshot(); + await browser.close(); + return screenshot; + } + + //Need to add interface + async getShorteningURL(shareUserCertificate, attributeArray): Promise { + const urlObject = { + schemaId: shareUserCertificate.schemaId, + credDefId: shareUserCertificate.credDefId, + attribute: attributeArray, + credentialId:shareUserCertificate.credentialId, + email:attributeArray.find((attr) => 'email' in attr).email + }; + + const qrCodeOptions = { type: 'image/png' }; + const encodedData = Buffer.from(JSON.stringify(shareUserCertificate)).toString('base64'); + const qrCode = await QRCode.toDataURL(`https://credebl.id/c_v?${encodedData}`, qrCodeOptions); + + return qrCode; + } /** * * @param acceptRejectInvitation @@ -1123,9 +1146,9 @@ export class UserService { const payload = { userId, keycloakUserId, orgId, invitationId, status, email }; - const invitationsData = await this.natsClient - .send(this.userServiceProxy, pattern, payload) - + const invitationsData = await this.userServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1152,6 +1175,7 @@ export class UserService { */ async getOrgUsers(orgId: string, pageNumber: number, pageSize: number, search: string): Promise { try { + const query = { userOrgRoles: { some: { orgId } @@ -1199,33 +1223,24 @@ export class UserService { async checkUserExist(email: string): Promise { try { const userDetails = await this.userRepository.checkUniqueUserExist(email.toLowerCase()); - let userVerificationDetails; - if (userDetails) { - userVerificationDetails = { - isEmailVerified: userDetails.isEmailVerified, - isFidoVerified: userDetails.isFidoVerified, - isRegistrationCompleted: null !== userDetails.keycloakUserId && undefined !== userDetails.keycloakUserId, - message: '', - userId: userDetails.id - }; - } if (userDetails && !userDetails.isEmailVerified) { - userVerificationDetails.message = ResponseMessages.user.error.verificationAlreadySent; - return userVerificationDetails; + throw new ConflictException(ResponseMessages.user.error.verificationAlreadySent); } else if (userDetails && userDetails.keycloakUserId) { - userVerificationDetails.message = ResponseMessages.user.error.exists; - return userVerificationDetails; + throw new ConflictException(ResponseMessages.user.error.exists); } else if (userDetails && !userDetails.keycloakUserId && userDetails.supabaseUserId) { - userVerificationDetails.message = ResponseMessages.user.error.exists; - return userVerificationDetails; + throw new ConflictException(ResponseMessages.user.error.exists); } else if (null === userDetails) { return { isRegistrationCompleted: false, - isEmailVerified: false, - userId: null, - message: ResponseMessages.user.error.notFound + isEmailVerified: false }; } else { + const userVerificationDetails = { + isEmailVerified: userDetails.isEmailVerified, + isFidoVerified: userDetails.isFidoVerified, + isRegistrationCompleted: null !== userDetails.keycloakUserId && undefined !== userDetails.keycloakUserId + + }; return userVerificationDetails; } } catch (error) { @@ -1252,14 +1267,36 @@ export class UserService { throw new BadRequestException(ResponseMessages.user.error.notUpdatePlatformSettings); } - return ResponseMessages.user.success.platformSettings; + const ecosystemobj = {}; + + if (EcosystemConfigSettings.ENABLE_ECOSYSTEM in platformSettings) { + ecosystemobj[EcosystemConfigSettings.ENABLE_ECOSYSTEM] = platformSettings.enableEcosystem; + } + + if (EcosystemConfigSettings.MULTI_ECOSYSTEM in platformSettings) { + ecosystemobj[EcosystemConfigSettings.MULTI_ECOSYSTEM] = platformSettings.multiEcosystemSupport; + } + + const eosystemKeys = Object.keys(ecosystemobj); + + if (0 === eosystemKeys.length) { + return ResponseMessages.user.success.platformEcosystemettings; + } + + const ecosystemSettings = await this.userRepository.updateEcosystemSettings(eosystemKeys, ecosystemobj); + + if (!ecosystemSettings) { + throw new BadRequestException(ResponseMessages.user.error.notUpdateEcosystemSettings); + } + + return ResponseMessages.user.success.platformEcosystemettings; } catch (error) { this.logger.error(`update platform settings: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } - async getPlatformSettings(): Promise { + async getPlatformEcosystemSettings(): Promise { try { const platformSettings = {}; const platformConfigSettings = await this.userRepository.getPlatformSettings(); @@ -1268,7 +1305,14 @@ export class UserService { throw new BadRequestException(ResponseMessages.user.error.platformSetttingsNotFound); } + const ecosystemConfigSettings = await this.userRepository.getEcosystemSettings(); + + if (!ecosystemConfigSettings) { + throw new BadRequestException(ResponseMessages.user.error.ecosystemSetttingsNotFound); + } + platformSettings['platform_config'] = platformConfigSettings; + platformSettings['ecosystem_config'] = ecosystemConfigSettings; return platformSettings; } catch (error) { @@ -1277,23 +1321,9 @@ export class UserService { } } - async updateOrgDeletedActivity( - orgId: string, - userId: string, - deletedBy: string, - recordType: RecordType, - userEmail: string, - txnMetadata: object - ): Promise { + async updateOrgDeletedActivity(orgId: string, userId: string, deletedBy: string, recordType: RecordType, userEmail: string, txnMetadata: object): Promise { try { - return await this.userRepository.updateOrgDeletedActivity( - orgId, - userId, - deletedBy, - recordType, - userEmail, - txnMetadata - ); + return await this.userRepository.updateOrgDeletedActivity(orgId, userId, deletedBy, recordType, userEmail, txnMetadata); } catch (error) { this.logger.error(`In updateOrgDeletedActivity : ${JSON.stringify(error)}`); throw error; @@ -1313,6 +1343,7 @@ export class UserService { async getUserKeycloakIdByEmail(userEmails: string[]): Promise { try { + const getkeycloakUserIds = await this.userRepository.getUserKeycloak(userEmails); return getkeycloakUserIds; } catch (error) { @@ -1338,33 +1369,4 @@ export class UserService { throw error; } } - - // eslint-disable-next-line camelcase - async getuserOrganizationByUserId(userId: string): Promise { - try { - const getOrganizationDetails = await this.userRepository.handleGetUserOrganizations(userId); - - if (!getOrganizationDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } - - return getOrganizationDetails; - } catch (error) { - this.logger.error(`Error in getuserOrganizationByUserId: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } - - async logout(logoutUserDto: ISessions): Promise { - try { - if (logoutUserDto?.sessions) { - await this.userRepository.destroySession(logoutUserDto?.sessions); - } - - return 'user logged out successfully'; - } catch (error) { - this.logger.error(`Error in logging out session: ${error}`); - throw new RpcException(error.response ? error.response : error); - } - } } diff --git a/apps/user/templates/arbiter-template.ts b/apps/user/templates/arbiter-template.ts new file mode 100644 index 000000000..962c39710 --- /dev/null +++ b/apps/user/templates/arbiter-template.ts @@ -0,0 +1,97 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class ArbiterTemplate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getArbiterTemplate(attributes: Attribute[]): Promise { + try { + const [name] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'full_name')?.full_name ?? ''; + return [name]; + }); + return ` + + + + + + + + + + + + + + + +
    + background +
    +
    +

    CERTIFICATE

    +

    OF RECOGNITION

    +
    + +

    IS PROUDLY PRESENTED TO

    +

    ${name}

    + + +

    has served as an Arbiter at the + IAM World Memory Championship 2023. +

    +

    +

    +

    Your dedication, professionalism, and impartiality as an Arbiter

    +

    have significantly contributed to the fair and smooth conduct

    +

    of the championship. Your commitment to upholding the

    +

    highest standards of integrity and sportsmanship has

    +

    played a crucial role in maintaining the credibility of the competition.

    + +

    +
    Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
    +
    Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
    +
    +
    + + + `; + } catch {} + } +} diff --git a/apps/user/templates/degree-template.ts b/apps/user/templates/degree-template.ts new file mode 100644 index 000000000..4fdf8da74 --- /dev/null +++ b/apps/user/templates/degree-template.ts @@ -0,0 +1,248 @@ +import { Attribute, IIssueCertificate } from '../interfaces/user.interface'; + +export class DegreeCertificateTemplate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getDegreeCertificateTemplate(attributes: Attribute[], qrCode: string): Promise { + try { + const { + slNo, + date, + studentName, + programme, + courseDetails, + universityName, + type, + semester, + academicYear, + currentSemesterPerformanceCreditsEarned, + cumulativeSemesterPerformanceCreditsEarned, + currentSemesterPerformanceSGA, + cumulativeSemesterPerformanceSGA, + registrationNo, + year + } = await Promise.all(attributes).then((attributes) => { + const slNo = this.findAttributeByName(attributes, 'slNo')?.slNo ?? ''; + const date = this.findAttributeByName(attributes, 'date')?.date ?? ''; + const studentName = this.findAttributeByName(attributes, 'studentName')?.studentName ?? ''; + const programme = this.findAttributeByName(attributes, 'programme')?.programme ?? ''; + const courseDetails = this.findAttributeByName(attributes, 'courseDetails')?.courseDetails ?? ''; + const universityName = this.findAttributeByName(attributes, 'universityName')?.universityName ?? ''; + const semester = this.findAttributeByName(attributes, 'semester')?.semester ?? ''; + const type = this.findAttributeByName(attributes, 'type')?.type ?? ''; + const academicYear = this.findAttributeByName(attributes, 'academicYear')?.academicYear ?? ''; + const year = this.findAttributeByName(attributes, 'year')?.year ?? ''; + const currentSemesterPerformanceCreditsEarned = + this.findAttributeByName(attributes, 'currentSemesterPerformance-creditsEarned')?.[ + 'currentSemesterPerformance-creditsEarned' + ] ?? ''; + const cumulativeSemesterPerformanceCreditsEarned = + this.findAttributeByName(attributes, 'commulativeSemesterPerformance-creditsEarned')?.[ + 'cumulativeSemesterPerformance-creditsEarned' + ] ?? ''; + const currentSemesterPerformanceSGA = + this.findAttributeByName(attributes, 'currentSemesterPerformance-SGA')?.['currentSemesterPerformance-SGA'] ?? + ''; + const cumulativeSemesterPerformanceSGA = + this.findAttributeByName(attributes, 'commulativeSemesterPerformance-SGA')?.[ + 'commulativeSemesterPerformance-SGA' + ] ?? ''; + const registrationNo = this.findAttributeByName(attributes, 'registrationNo')?.registrationNo ?? ''; + + const refinedObj = { + slNo, + date, + studentName, + programme, + courseDetails, + universityName, + type, + semester, + academicYear, + year, + currentSemesterPerformanceCreditsEarned, + cumulativeSemesterPerformanceCreditsEarned, + currentSemesterPerformanceSGA, + cumulativeSemesterPerformanceSGA, + registrationNo + }; + + return refinedObj; + }); + + const courseDetailsJSON: IIssueCertificate[] = courseDetails && JSON.parse(courseDetails); + return ` + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + SL.No : ${slNo} +
    +
    +
    +

    ${universityName}

    +

    Pune, Maharashtra, (M.S.) India, 410061

    + +
    +
    +

    ${type}

    +

    (Academic Year ${academicYear})

    +
    +
    + + QR Code +
    +
    +
    +
    +
    +
    Name of the Student : ${studentName}
    +
    Programme : ${programme}
    +
    +
    Year : ${year}
    +
    Semester : ${semester}
    +
    Registration Number : ${registrationNo}
    +
    +
    +
    + + + + + + + + + + + + + + + + + ${ + courseDetailsJSON?.map((item: IIssueCertificate) => ( + ` + + + + + + + ` + )) + } + +
    Course CodeCourseTheoryPractical
    Grade CreditsObtained EarnedGrade CreditsObtained Earned
    ${item.courseCode}${item.courseName}${item.theoryGradeCredits}${item.theoryObtainedEarned}${item.practicalGradeCredits}${item.practicalObtainedEarned}
    + + + + + + + + + + + + + + + + + + + + + +
    Current Semester PerformanceCummulative Semester Performance
    Credits EarnedSGACredits EarnedSGA
    ${currentSemesterPerformanceCreditsEarned}${currentSemesterPerformanceSGA}${cumulativeSemesterPerformanceCreditsEarned}${cumulativeSemesterPerformanceSGA}
    +
    +
    +
    Date: ${date}
    +
    + +
    +
    + Controller of Examinations +
    +
    +
    +
    +
    + + + `; + } catch (error) { + // eslint-disable-next-line no-console + console.log(`Degree temlpate ERROR::: ${JSON.stringify(error)}`); + } + } +} diff --git a/apps/user/templates/event-certificates.ts b/apps/user/templates/event-certificates.ts new file mode 100644 index 000000000..bed44507c --- /dev/null +++ b/apps/user/templates/event-certificates.ts @@ -0,0 +1,82 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class EventCertificate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getCertificateWinner(attributes: Attribute[], QRDetails): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [name, description, category] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'name').name ?? ''; + const description = this.findAttributeByName(attributes, 'description').description ?? ''; + const category = this.findAttributeByName(attributes, 'category').category ?? ''; + return [name, description, category]; + }); + return ` + + + + + + + + Document + + + +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +

    Certificate of Appreciation

    +

    - ${category} -

    +
    +

    this certificate is proudly presented to

    +

    ${name}

    +

    ${description}

    +

    ~ 23rd March 2024 ~

    +
    +
    + QR Code +
    +
    +
    + +
    +

    Kirankalyan Kulkarni

    +

    CEO & Co-Founder

    +
    +
    +
    + +
    +

    Ajay Jadhav

    +

    CTO & Co-Founder

    +
    +
    +
    +
    +
    +
    + + + `; + } catch { } + } +} \ No newline at end of file diff --git a/apps/user/templates/event-pinnacle.ts b/apps/user/templates/event-pinnacle.ts new file mode 100644 index 000000000..236478765 --- /dev/null +++ b/apps/user/templates/event-pinnacle.ts @@ -0,0 +1,80 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class EventPinnacle { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getPinnacleWinner(attributes: Attribute[], qrCode): Promise { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [name, description] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'name').name ?? ''; + const description = this.findAttributeByName(attributes, 'description').description ?? ''; + return [name, description]; + }); + return ` + + + + + + + + Document + + + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +
    +
    + +
    +

    Certificate of Appreciation

    +

    - Pinnacle Performer -

    +
    +

    this certificate is proudly presented to

    +

    ${name}

    +

    ${description}

    +

    ~ 23rd March 2024 ~

    +
    +
    +
    + +
    +

    Kirankalyan Kulkarni

    +

    CEO & Co-Founder

    +
    +
    +
    + +
    +

    Ajay Jadhav

    +

    CTO & Co-Founder

    +
    +
    +
    +
    +
    +
    + + + `; + } catch { } + } +} \ No newline at end of file diff --git a/apps/user/templates/participant-template.ts b/apps/user/templates/participant-template.ts new file mode 100644 index 000000000..d994bd2fd --- /dev/null +++ b/apps/user/templates/participant-template.ts @@ -0,0 +1,93 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class ParticipantTemplate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getParticipantTemplate(attributes: Attribute[]): Promise { + + try { + const [name] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'full_name')?.full_name ?? ''; + return [name]; + }); + + return ` + + + + + + + + + + + + + + + +
    + background +
    +
    +

    CERTIFICATE

    +

    OF ACHIEVEMENT

    +
    + +

    IS PROUDLY PRESENTED TO

    +

    ${name}

    + + for successfully participating in the + IAM World Memory Championship 2023. +

    +

    +

    We acknowledge your dedication, hard work, and

    +

    exceptional memory skills demonstrated during the competition.

    +

    +
    Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
    +
    Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
    +
    +
    + + + `; + } catch (error) {} + } +} diff --git a/apps/user/templates/reset-password-template.ts b/apps/user/templates/reset-password-template.ts index 3e4ced6d6..ec05ecdde 100644 --- a/apps/user/templates/reset-password-template.ts +++ b/apps/user/templates/reset-password-template.ts @@ -1,22 +1,12 @@ import * as url from 'url'; export class URLUserResetPasswordTemplate { - public getUserResetPasswordTemplate( - email: string, - platform: string, - brandLogoUrl: string, - uiEndpoint: string, - verificationCode: string, - clientAlias?: string - ): string { - const endpoint = uiEndpoint || process.env.FRONT_END_URL; + public getUserResetPasswordTemplate(email: string, verificationCode: string): string { + const endpoint = `${process.env.FRONT_END_URL}`; const apiUrl = url.parse( - `${endpoint}/reset-password?verificationCode=${verificationCode}&email=${encodeURIComponent(email)}${clientAlias ? `&clientAlias=${clientAlias}` : ''}` + `${endpoint}/reset-password?verificationCode=${verificationCode}&email=${encodeURIComponent(email)}` ); - - const logoUrl = brandLogoUrl || process.env.BRAND_LOGO; - const poweredBy = platform || process.env.POWERED_BY; - + const validUrl = apiUrl.href.replace('/:', ':'); try { @@ -33,7 +23,7 @@ export class URLUserResetPasswordTemplate {
    - ${platform} logo + ${process.env.PLATFORM_NAME} logo
    @@ -60,13 +50,14 @@ export class URLUserResetPasswordTemplate {

    - © ${poweredBy} + © ${process.env.POWERED_BY}

    `; + } catch (error) {} } } diff --git a/apps/user/templates/user-email-template.ts b/apps/user/templates/user-email-template.ts index bed2befb6..31cfbcdab 100644 --- a/apps/user/templates/user-email-template.ts +++ b/apps/user/templates/user-email-template.ts @@ -1,26 +1,14 @@ export class URLUserEmailTemplate { - public getUserURLTemplate( - email: string, - verificationCode: string, - brandLogoUrl: string, - platformName: string, - redirectTo?: string, - clientAlias?: string - ): string { - const baseDomain = `${process.env.FRONT_END_URL}`; - const apiUrl = new URL('/verify-email-success', baseDomain); + public getUserURLTemplate(email: string, verificationCode: string, redirectUrl: string, clientId: string, brandLogoUrl:string, platformName:string): string { + + const apiUrl = new URL( + clientId === process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID ? '/verify-email-success' : '', + redirectUrl + ); apiUrl.searchParams.append('verificationCode', verificationCode); apiUrl.searchParams.append('email', encodeURIComponent(email)); - if (redirectTo) { - apiUrl.searchParams.append('redirectTo', redirectTo); - } - - if (clientAlias) { - apiUrl.searchParams.append('clientAlias', clientAlias); - } - const validUrl = apiUrl.href; const logoUrl = brandLogoUrl || process.env.BRAND_LOGO; @@ -76,8 +64,6 @@ export class URLUserEmailTemplate { `; - } catch (error) { - throw new Error('Error creating email verification template'); - } + } catch (error) {} } } diff --git a/apps/user/templates/winner-template.ts b/apps/user/templates/winner-template.ts new file mode 100644 index 000000000..aeb66b484 --- /dev/null +++ b/apps/user/templates/winner-template.ts @@ -0,0 +1,98 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class WinnerTemplate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getWinnerTemplate(attributes: Attribute[]): Promise { + try { + const [name, position, discipline, category] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'full_name')?.full_name ?? ''; + const position = this.findAttributeByName(attributes, 'position')?.position ?? ''; + const discipline = this.findAttributeByName(attributes, 'discipline')?.discipline ?? ''; + const category = this.findAttributeByName(attributes, 'category')?.category ?? ''; + const date = this.findAttributeByName(attributes, 'issued_date')?.issued_date ?? ''; + return [name, position, discipline, category, date]; + }); + return ` + + + + + + + + + + + + + + + +
    + background +
    +
    +

    CERTIFICATE

    +

    OF EXCELLENCE

    +
    + +

    IS PROUDLY PRESENTED TO

    +

    ${name}

    + +

    has secured ${position} position for ${discipline}

    +

    in ${category} category at the

    +

    + IAM World Memory Championship 2023. +

    +

    +

    +

    We acknowledge your dedication, hard work, and

    +

    exceptional memory skills demonstrated during the competition.

    +

    +
    Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
    +
    Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
    +
    +
    + + + + `; + } catch { } + } +} diff --git a/apps/user/templates/world-record-template.ts b/apps/user/templates/world-record-template.ts new file mode 100644 index 000000000..27c9fbfda --- /dev/null +++ b/apps/user/templates/world-record-template.ts @@ -0,0 +1,95 @@ +import { Attribute } from '../interfaces/user.interface'; + +export class WorldRecordTemplate { + findAttributeByName(attributes: Attribute[], name: string): Attribute { + return attributes.find((attr) => name in attr); + } + + async getWorldRecordTemplate(attributes: Attribute[]): Promise { + try { + const [name, discipline] = await Promise.all(attributes).then((attributes) => { + const name = this.findAttributeByName(attributes, 'full_name')?.full_name ?? ''; + const discipline = this.findAttributeByName(attributes, 'discipline')?.discipline ?? ''; + return [name, discipline]; + }); + return ` + + + + + + + + + + + + + + + +
    + background +
    +
    +

    CERTIFICATE

    +

    OF WORLD RECORD

    +
    + +

    IS PROUDLY PRESENTED TO

    +

    ${name}

    + + for successfully creating the world record in the + ${discipline} +

    discipline during the + IAM World Memory Championship 2023. +

    +

    +

    +

    We acknowledge your dedication, hard work, and

    +

    exceptional memory skills demonstrated during the competition.

    +

    +
    Date: 24, 25, 26 November 2023 | Place: Cidco Exhibition Centre, Navi Mumbai, India
    +
    Blockchain-based certificate issued using ${process.env.PLATFORM_WEB_URL}, by ${process.env.POWERED_BY}
    +
    +
    + + + `; + } catch {} + } +} diff --git a/apps/user/test/app.e2e-spec.ts b/apps/user/test/app.e2e-spec.ts index ab51d4a94..53e0deb0b 100644 --- a/apps/user/test/app.e2e-spec.ts +++ b/apps/user/test/app.e2e-spec.ts @@ -15,10 +15,8 @@ describe('UserController (e2e)', () => { await app.init(); }); - it('/ (GET)', () => { - return request(app.getHttpServer()) + it('/ (GET)', () => request(app.getHttpServer()) .get('/') .expect(200) - .expect('Hello World!'); - }); + .expect('Hello World!')); }); diff --git a/apps/utility/src/main.ts b/apps/utility/src/main.ts index 3000eedd3..ee165502d 100644 --- a/apps/utility/src/main.ts +++ b/apps/utility/src/main.ts @@ -5,7 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { getNatsOptions } from '@credebl/common/nats.config'; import { UtilitiesModule } from './utilities.module'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -16,7 +15,6 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.UTILITY_SERVICE, process.env.UTILITIES_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/utility/src/utilities.module.ts b/apps/utility/src/utilities.module.ts index 2f547234f..51c31d6dd 100644 --- a/apps/utility/src/utilities.module.ts +++ b/apps/utility/src/utilities.module.ts @@ -9,10 +9,6 @@ import { UtilitiesService } from './utilities.service'; import { UtilitiesRepository } from './utilities.repository'; import { AwsService } from '@credebl/aws'; import { CommonConstants } from '@credebl/common/common.constant'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; @Module({ imports: [ @@ -24,8 +20,6 @@ import { ContextInterceptorModule } from '@credebl/context/contextInterceptorMod } ]), CommonModule, - GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, CacheModule.register() ], controllers: [UtilitiesController], diff --git a/apps/utility/src/utilities.repository.ts b/apps/utility/src/utilities.repository.ts index 7727d1b8e..17488aa84 100644 --- a/apps/utility/src/utilities.repository.ts +++ b/apps/utility/src/utilities.repository.ts @@ -1,7 +1,7 @@ -import { PrismaService } from "@credebl/prisma-service"; -import { Injectable, Logger } from "@nestjs/common"; +import { PrismaService } from '@credebl/prisma-service'; +import { Injectable, Logger } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { shortening_url } from "@prisma/client"; +import { shortening_url } from '@prisma/client'; @Injectable() export class UtilitiesRepository { diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 2e259a785..2c89e49f7 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -1,300 +1,246 @@ import { AutoAccept } from '@credebl/enum/enum'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -export interface IProofRequestAttribute { - attributeName?: string; - attributeNames?: string[]; - condition?: string; - value?: string; - credDefId?: string; - schemaId?: string; - credentialName?: string; +interface IProofRequestAttribute { + attributeName?: string; + attributeNames?:string[]; + condition?: string; + value?: string; + credDefId?: string; + schemaId?: string; + credentialName?: string; } export enum ProofRequestType { - INDY = 'indy', - PRESENTATIONEXCHANGE = 'presentationExchange' + INDY = 'indy', + PRESENTATIONEXCHANGE = 'presentationExchange' } export interface IRequestProof { - orgId: string; - version: string; - connectionId?: string | string[]; - attributes?: IProofRequestAttribute[]; - type: ProofRequestType; - presentationDefinition?: IProofRequestPresentationDefinition; - comment: string; - autoAcceptProof: AutoAccept; - protocolVersion?: string; - emailId?: string[]; - goalCode?: string; - parentThreadId?: string; - willConfirm?: boolean; + orgId: string; + connectionId?: string; + attributes?: IProofRequestAttribute[]; + type: ProofRequestType; + presentationDefinition?:IProofRequestPresentationDefinition; + comment: string; + autoAcceptProof: AutoAccept; + protocolVersion?: string; + emailId?: string[]; + goalCode?: string; + parentThreadId?: string; + willConfirm?: boolean; } export interface IGetAllProofPresentations { - url: string; - apiKey: string; + url: string; + apiKey: string; } export interface IGetProofPresentationById { - url: string; - apiKey?: string; - orgId?: string; + url: string; + apiKey?: string; + orgId?: string; } export interface IVerifyPresentation { - url: string; - apiKey?: string; - orgId?: string; + url: string; + apiKey?: string; + orgId?: string; } export interface IVerifiedProofData { - url: string; - apiKey?: string; - orgId?: string; + url: string; + apiKey?: string; + orgId?: string } export interface IProofPresentationData { - proofId: string; - orgId: string; - user: IUserRequest; + proofId: string; + orgId: string; + user: IUserRequest; } interface IProofFormats { - indy: IndyProof; + indy: IndyProof } interface IndyProof { - name: string; - version: string; - requested_attributes: IRequestedAttributes; - requested_predicates: IRequestedPredicates; + name: string; + version: string; + requested_attributes: IRequestedAttributes; + requested_predicates: IRequestedPredicates; } interface IRequestedAttributes { - [key: string]: IRequestedAttributesName; + [key: string]: IRequestedAttributesName; } interface IRequestedAttributesName { - name?: string; - names?: string; - restrictions: IRequestedRestriction[]; + name?: string; + names?: string; + restrictions: IRequestedRestriction[] } interface IRequestedPredicates { - [key: string]: IRequestedPredicatesName; + [key: string]: IRequestedPredicatesName; } interface IRequestedPredicatesName { - name: string; - restrictions: IRequestedRestriction[]; + name: string; + restrictions: IRequestedRestriction[] } interface IRequestedRestriction { - cred_def_id?: string; - schema_id?: string; - schema_issuer_did?: string; - schema_name?: string; - issuer_did?: string; - schema_version?: string; + cred_def_id?: string; + schema_id?: string; + schema_issuer_did?: string; + schema_name?: string; + issuer_did?: string; + schema_version?: string; } export interface ISchema { - uri: string; + uri:string; } -export interface IFields { - path: string[]; + +export interface IFilter { + type: string; + pattern: string; } +export interface IFields { + path: string[]; + filter: IFilter; + } export interface IConstraints { - fields: IFields[]; -} + fields: IFields[]; + } export interface IInputDescriptors { - id: string; - name?: string; - purpose?: string; - schema: ISchema[]; - constraints?: IConstraints; + + id:string; + name?:string; + purpose?:string; + schema:ISchema[]; + constraints?:IConstraints; + } export interface IProofRequestPresentationDefinition { - id: string; - name: string; - purpose?: string; - input_descriptors: IInputDescriptors[]; + id:string; + name: string; + purpose: string; + input_descriptors:IInputDescriptors[]; } export interface IPresentationExchange { - presentationDefinition: IProofRequestPresentationDefinition; + presentationDefinition:IProofRequestPresentationDefinition; + } export interface IPresentationExchangeProofFormats { - presentationExchange?: IPresentationExchange; - indy?: IndyProof; + presentationExchange? : IPresentationExchange; + indy?: IndyProof } export interface ISendPresentationExchangeProofRequestPayload { - protocolVersion: string; - comment: string; - parentThreadId?: string; - proofFormats: IPresentationExchangeProofFormats; - autoAcceptProof: string; - label?: string; + protocolVersion: string; + comment: string; + parentThreadId?: string; + proofFormats: IPresentationExchangeProofFormats; + autoAcceptProof: string; + label?: string; } export interface IPresentationExchangeProofRequestPayload { - url: string; - apiKey?: string; - proofRequestPayload: ISendPresentationExchangeProofRequestPayload; - orgId?: string; + url: string; + apiKey?: string; + proofRequestPayload: ISendPresentationExchangeProofRequestPayload; + orgId?: string; } export interface ISendProofRequestPayload { - protocolVersion?: string; - comment?: string; - connectionId?: string; - proofFormats?: IProofFormats; - autoAcceptProof?: AutoAccept; - label?: string; - goalCode?: string; - // TODO: [Credo-ts] Issue with parentThreadId in creating an OOB proof request. - // This causes failures in OOB connection establishment. - // parentThreadId?: string; - willConfirm?: boolean; - imageUrl?: string; - emailId?: string[]; - isShortenUrl?: boolean; - type?: string; - orgId?: string; - presentationDefinition?: IProofRequestPresentationDefinition; - reuseConnection?: boolean; - recipientKey?: string; - invitationDid?: string; + protocolVersion?: string; + comment?: string; + connectionId?: string; + proofFormats?: IProofFormats; + autoAcceptProof?: AutoAccept; + label?: string; + goalCode?: string; + parentThreadId?: string; + willConfirm?: boolean; + imageUrl?: string; + emailId?: string[] + isShortenUrl?: boolean; + type?:string; + orgId?: string; + presentationDefinition?:IProofRequestPresentationDefinition; + reuseConnection?: boolean; + recipientKey?:string; + invitationDid?: string } export interface IWSendProofRequestPayload { - protocolVersion?: string; - comment?: string; - connectionId?: string; - proofFormats?: IProofFormats; - autoAcceptProof?: string; - label?: string; - goalCode?: string; - parentThreadId?: string; - willConfirm?: boolean; - imageUrl?: string; - emailId?: string[]; - type?: string; - presentationDefinition?: IProofRequestPresentationDefinition; + protocolVersion?: string; + comment?: string; + connectionId?: string; + proofFormats?: IProofFormats; + autoAcceptProof?: string; + label?: string; + goalCode?: string; + parentThreadId?: string; + willConfirm?: boolean; + imageUrl?: string; + emailId?: string[]; + type?:string; + presentationDefinition?:IProofRequestPresentationDefinition; } export interface IProofRequestPayload { - url: string; - apiKey?: string; - orgId?: string; - proofRequestPayload: ISendProofRequestPayload | ISendPresentationExchangeProofRequestPayload; + url: string; + apiKey?: string; + orgId?: string; + proofRequestPayload: ISendProofRequestPayload | ISendPresentationExchangeProofRequestPayload; } interface IWebhookPresentationProof { - threadId: string; - state: string; - connectionId; + threadId: string; + state: string; + connectionId } export interface IWebhookProofPresentation { - metadata: object; - _tags: IWebhookPresentationProof; - id: string; - createdAt: string; - protocolVersion: string; - state: string; - connectionId: string; - presentationId: string; - threadId: string; - parentThreadId?: string; - autoAcceptProof: string; - updatedAt: string; - isVerified: boolean; - contextCorrelationId: string; - errorMessage?: string; + metadata: object; + _tags: IWebhookPresentationProof; + id: string; + createdAt: string; + protocolVersion: string; + state: string; + connectionId: string; + presentationId: string; + threadId: string; + parentThreadId?: string; + autoAcceptProof: string; + updatedAt: string; + isVerified: boolean; + contextCorrelationId: string; } export interface IProofPresentation { - proofPresentationPayload: IWebhookProofPresentation; - orgId: string; + proofPresentationPayload: IWebhookProofPresentation; + orgId: string; } export interface IProofRequests { - proofRequestsSearchCriteria: IProofRequestSearchCriteria; - user: IUserRequest; - orgId: string; + proofRequestsSearchCriteria: IProofRequestSearchCriteria; + user: IUserRequest; + orgId: string; } export interface IProofRequestSearchCriteria { - pageNumber: number; - pageSize: number; - sortField: string; - sortBy: string; - search: string; -} - -export interface IInvitation { - invitationUrl?: string; - deepLinkURL?: string; -} - -export interface IProofRequestData { - goalCode?: string; - version: string; - parentThreadId?: string; - willConfirm?: boolean; - protocolVersion?: string; - proofFormats?: IProofFormat; - orgId: string; - connectionId?: string | string[]; - attributes?: IProofRequestAttribute[]; - type: ProofRequestType; - presentationDefinition?: IProofRequestPresentationDefinition; - comment: string; - autoAcceptProof: AutoAccept; -} -export interface IProofFormat { - indy: Indy; + pageNumber: number; + pageSize: number; + sortField: string; + sortBy: string; + search: string; } -export interface Indy { - attributes: IProofAttributesData[]; -} - -export interface IProofAttributesData { - attributeName: string; - attributeNames?: string[]; - condition: string; - value: string; - credDefId: string; - schemaId: string; -} - -export interface IEmailResponse { - email: string; - isEmailSent: boolean; - outOfBandRecordId: string; - proofRecordThId: string; -} - -export enum ProofRequest { - presentationReceived = 'presentation-received', - offerReceived = 'offer-received', - declined = 'decliend', - requestSent = 'request-sent', - requestReceived = 'request-received', - credentialIssued = 'credential-issued', - credentialReceived = 'credential-received', - done = 'done', - abandoned = 'abandoned' -} - -export enum ProofRequestState { - requestSent = 'Requested', - requestReceived = 'Received', - done = 'Verified', - abandoned = 'Declined', - presentationReceived = 'Presentation Received' -} +export interface IInvitation{ + invitationUrl?: string; + deepLinkURL?: string; +} \ No newline at end of file diff --git a/apps/verification/src/main.ts b/apps/verification/src/main.ts index d355b3971..b8ff0ea23 100644 --- a/apps/verification/src/main.ts +++ b/apps/verification/src/main.ts @@ -5,7 +5,6 @@ import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { VerificationModule } from './verification.module'; import { getNatsOptions } from '@credebl/common/nats.config'; import { CommonConstants } from '@credebl/common/common.constant'; -import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter'; const logger = new Logger(); @@ -16,7 +15,7 @@ async function bootstrap(): Promise { options: getNatsOptions(CommonConstants.VERIFICATION_SERVICE, process.env.VERIFICATION_NKEY_SEED) }); - app.useLogger(app.get(NestjsLoggerServiceAdapter)); + app.useGlobalFilters(new HttpExceptionFilter()); await app.listen(); diff --git a/apps/verification/src/repositories/verification.repository.ts b/apps/verification/src/repositories/verification.repository.ts index 61ae918b3..7de0cd8b9 100644 --- a/apps/verification/src/repositories/verification.repository.ts +++ b/apps/verification/src/repositories/verification.repository.ts @@ -1,20 +1,13 @@ -import { - IEmailResponse, - IProofPresentation, - IProofRequestSearchCriteria, - ProofRequest, - ProofRequestState -} from '../interfaces/verification.interface'; -import { IProofPresentationsListCount, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; +import { ResponseMessages } from '@credebl/common/response-messages'; +import { PrismaService } from '@credebl/prisma-service'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { Prisma, agent_invitations, org_agents, organisation, platform_config, presentations } from '@prisma/client'; - -import { CommonService } from '@credebl/common'; +import { agent_invitations, org_agents, organisation, platform_config } from '@prisma/client'; +import { IProofPresentation, IProofRequestSearchCriteria } from '../interfaces/verification.interface'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { PrismaService } from '@credebl/prisma-service'; -import { ResponseMessages } from '@credebl/common/response-messages'; +import { IProofPresentationsListCount, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; import { SortValue } from '@credebl/enum/enum'; +import { CommonService } from '@credebl/common'; @Injectable() export class VerificationRepository { @@ -49,30 +42,6 @@ export class VerificationRepository { } } - /** - * Get issuerId - * @param issuerId - * @returns org Id - */ - async getOrgDetails(issuerId: string): Promise { - try { - const orgId = await this.prisma.org_agents.findFirst({ - where: { - orgDid: issuerId - }, - select: { - orgId: true, - orgDid: true - } - }); - - return orgId; - } catch (error) { - this.logger.error(`[getOrgId] - error in getting orgId : ${error.message} `); - throw error; - } - } - // eslint-disable-next-line camelcase async getOrganizationByTenantId(tenantId: string): Promise { try { @@ -93,45 +62,16 @@ export class VerificationRepository { proofRequestsSearchCriteria: IProofRequestSearchCriteria ): Promise { try { - let verificationStateInfo = null; - - switch (proofRequestsSearchCriteria.search.toLowerCase()) { - case ProofRequestState.requestSent.toLowerCase(): - verificationStateInfo = ProofRequest.requestSent; - break; - case ProofRequestState.requestReceived.toLowerCase(): - verificationStateInfo = ProofRequest.requestReceived; - break; - case ProofRequestState.done.toLowerCase(): - verificationStateInfo = ProofRequest.done; - break; - case ProofRequestState.abandoned.toLowerCase(): - verificationStateInfo = ProofRequest.abandoned; - break; - case ProofRequestState.presentationReceived.toLowerCase(): - verificationStateInfo = ProofRequest.presentationReceived; - break; - default: - break; - } - - const whereClause: Prisma.presentationsWhereInput = { - orgId, - OR: [ - { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { emailId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { - connections: { - theirLabel: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } - } - }, - { state: { contains: verificationStateInfo ?? proofRequestsSearchCriteria.search, mode: 'insensitive' } } - ] - }; const proofRequestsList = await this.prisma.presentations.findMany({ - where: whereClause, + where: { + orgId, + OR: [ + { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } } + ] + }, select: { createDateTime: true, createdBy: true, @@ -141,13 +81,7 @@ export class VerificationRepository { id: true, presentationId: true, schemaId: true, - emailId: true, - errorMessage: true, - connections: { - select: { - theirLabel: true - } - } + emailId: true }, orderBy: { [proofRequestsSearchCriteria.sortField]: SortValue.ASC === proofRequestsSearchCriteria.sortBy ? 'asc' : 'desc' @@ -156,9 +90,15 @@ export class VerificationRepository { take: Number(proofRequestsSearchCriteria.pageSize), skip: (proofRequestsSearchCriteria.pageNumber - 1) * proofRequestsSearchCriteria.pageSize }); - const proofRequestsCount = await this.prisma.presentations.count({ - where: whereClause + where: { + orgId, + OR: [ + { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } } + ] + } }); return { proofRequestsCount, proofRequestsList }; @@ -168,17 +108,13 @@ export class VerificationRepository { } } - async getVerificationRecordsCount(orgId: string, state?: string): Promise { + async getVerificationRecordsCount(orgId: string): Promise { try { - const whereClause = { - orgId, - ...(state ? { state } : {}) - }; - const verificationRecordsCount = await this.prisma.presentations.count({ - where: whereClause + where: { + orgId + } }); - return verificationRecordsCount; } catch (error) { this.logger.error(`[get verification records by org Id] - error: ${JSON.stringify(error)}`); @@ -186,37 +122,45 @@ export class VerificationRepository { } } - async storeProofPresentation(payload: IProofPresentation): Promise { + // eslint-disable-next-line camelcase + async storeProofPresentation(payload: IProofPresentation): Promise { try { let encryptEmailId; let organisationId: string; + // eslint-disable-next-line camelcase + let agentOrg: org_agents; let schemaId; + const { proofPresentationPayload, orgId } = payload; //For Educreds if (proofPresentationPayload?.['proofData']?.presentation?.presentationExchange?.verifiableCredential) { - const emailId = - proofPresentationPayload?.['proofData']?.presentation?.presentationExchange?.verifiableCredential[0] - .credentialSubject?.email; + + const emailId = proofPresentationPayload?.['proofData']?.presentation?.presentationExchange?.verifiableCredential[0].credentialSubject?.email; encryptEmailId = await this.commonService.dataEncryption(emailId); } else { encryptEmailId = 'Not Available'; } - - //For Educreds + + //For Educreds if (proofPresentationPayload?.['proofData']?.request?.presentationExchange) { - schemaId = - proofPresentationPayload?.['proofData']?.request?.presentationExchange?.presentation_definition - ?.input_descriptors[0].schema[0].uri; + schemaId = proofPresentationPayload?.['proofData']?.request?.presentationExchange?.presentation_definition?.input_descriptors[0].schema[0].uri; } if ('default' !== proofPresentationPayload?.contextCorrelationId) { - const getOrganizationId = await this.getOrganizationByTenantId(proofPresentationPayload?.contextCorrelationId); - organisationId = getOrganizationId?.orgId; + agentOrg = await this.getOrganizationByTenantId(proofPresentationPayload?.contextCorrelationId); + organisationId = agentOrg?.orgId; + if (agentOrg?.orgId) { + organisationId = agentOrg?.orgId; + } else { + agentOrg = await this.getOrganizationByOrgId(orgId); + organisationId = orgId; + } } else { + agentOrg = await this.getOrganizationByOrgId(orgId); organisationId = orgId; } - const proofPresentationsDetails = await this.prisma.presentations.upsert({ + await this.prisma.presentations.upsert({ where: { threadId: proofPresentationPayload?.threadId }, @@ -226,8 +170,7 @@ export class VerificationRepository { isVerified: proofPresentationPayload.isVerified, lastChangedBy: organisationId, connectionId: proofPresentationPayload.connectionId, - emailId: encryptEmailId, - errorMessage: proofPresentationPayload.errorMessage + emailId: encryptEmailId }, create: { connectionId: proofPresentationPayload.connectionId, @@ -242,7 +185,7 @@ export class VerificationRepository { emailId: encryptEmailId } }); - return proofPresentationsDetails; + return agentOrg; } catch (error) { this.logger.error(`Error in get saveProofPresentationDetails: ${error.message} `); throw error; @@ -276,6 +219,20 @@ export class VerificationRepository { } } + // eslint-disable-next-line camelcase + async getOrganizationByOrgId(orgId: string): Promise { + try { + return this.prisma.org_agents.findFirst({ + where: { + orgId + } + }); + } catch (error) { + this.logger.error(`Error in getOrganization in issuance repository: ${error.message} `); + throw error; + } + } + async getOrgAgentType(orgAgentId: string): Promise { try { const { agent } = await this.prisma.org_agents_type.findFirst({ @@ -292,11 +249,20 @@ export class VerificationRepository { } // eslint-disable-next-line camelcase - async getInvitationDidByOrgId(orgId: string): Promise { + async getInvitationDidByOrgId(orgId: string): Promise { try { - return this.prisma.agent_invitations.findMany({ + return this.prisma.agent_invitations.findFirst({ where: { - orgId + AND: [ + { + orgId + }, + { + invitationDid: { + not: null + } + } + ] }, orderBy: { createDateTime: 'asc' // or 'desc' for descending order @@ -308,9 +274,11 @@ export class VerificationRepository { } } + async deleteVerificationRecordsByOrgId(orgId: string): Promise { try { - return await this.prisma.$transaction(async (prisma) => { + return await this.prisma.$transaction(async (prisma) => { + const recordsToDelete = await this.prisma.presentations.findMany({ where: { orgId } }); @@ -318,26 +286,13 @@ export class VerificationRepository { const deleteResult = await prisma.presentations.deleteMany({ where: { orgId } }); - - return { deleteResult, recordsToDelete }; + + return { deleteResult, recordsToDelete}; }); } catch (error) { - this.logger.error(`Error in deleting verification records: ${error.message}`); + this.logger.error(`Error in deleting verification records: ${error.message}`); throw error; } - } - - async saveEmail(emailList: IEmailResponse[]): Promise { - try { - for (const { proofRecordThId, email } of emailList) { - await this.prisma.presentations.updateMany({ - where: { threadId: proofRecordThId }, - data: { emailId: email } - }); - } - } catch (error) { - this.logger.error(`[Verification Save Email] - error: ${JSON.stringify(error)}`); - throw error; - } - } + } + } diff --git a/apps/verification/src/verification.controller.ts b/apps/verification/src/verification.controller.ts index 5b795d419..c728e3724 100644 --- a/apps/verification/src/verification.controller.ts +++ b/apps/verification/src/verification.controller.ts @@ -1,113 +1,88 @@ -import { - IProofPresentation, - IProofPresentationData, - IProofRequestData, - IProofRequests, - ISendProofRequestPayload -} from './interfaces/verification.interface'; -import { - IProofPresentationDetails, - IProofPresentationList, - IVerificationRecords -} from '@credebl/common/interfaces/verification.interface'; -import { presentations, user } from '@prisma/client'; - import { Controller } from '@nestjs/common'; -import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { MessagePattern } from '@nestjs/microservices'; import { VerificationService } from './verification.service'; +import { MessagePattern } from '@nestjs/microservices'; +import { IProofPresentation, IProofPresentationData, IProofRequests, IRequestProof, ISendProofRequestPayload } from './interfaces/verification.interface'; +import { IUserRequest } from '@credebl/user-request/user-request.interface'; +// eslint-disable-next-line camelcase +import { org_agents, user } from '@prisma/client'; +import { IProofPresentationDetails, IProofPresentationList, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; @Controller() export class VerificationController { - constructor(private readonly verificationService: VerificationService) {} + constructor(private readonly verificationService: VerificationService) { } /** * Get all proof presentations - * @param payload + * @param payload * @returns Get all proof presentation */ @MessagePattern({ cmd: 'get-all-proof-presentations' }) async getProofPresentations(payload: IProofRequests): Promise { - const { user, orgId, proofRequestsSearchCriteria } = payload; + const { user, orgId, proofRequestsSearchCriteria} = payload; return this.verificationService.getProofPresentations(user, orgId, proofRequestsSearchCriteria); } @MessagePattern({ cmd: 'get-verification-records' }) - async getVerificationRecordsByOrgId(payload: { orgId: string; userId: string }): Promise { + async getVerificationRecordsByOrgId(payload: { orgId: string, userId: string }): Promise { const { orgId } = payload; return this.verificationService.getVerificationRecords(orgId); } /** * Get proof presentation by proofId - * @param orgId - * @param proofId + * @param orgId + * @param proofId * @returns Proof presentation details by proofId */ @MessagePattern({ cmd: 'get-proof-presentations-by-proofId' }) - async getProofPresentationById(payload: { proofId: string; orgId: string; user: IUserRequest }): Promise { + async getProofPresentationById(payload: { proofId: string, orgId: string, user: IUserRequest }): Promise { return this.verificationService.getProofPresentationById(payload.proofId, payload.orgId); } - /** - * Get verified proof presentation by issuerId - * @param issuerId - * @returns Proof presentation details by issuerId - */ - @MessagePattern({ cmd: 'get-proof-presentation-details-by-issuerId' }) - async getProofPresentationByIssuerId(payload: { issuerId: string; user: IUserRequest }): Promise { - return this.verificationService.getProofPresentationByIssuerId(payload.issuerId); - } - /** * Send proof request - * @param orgId + * @param orgId * @returns Requested proof presentation details */ @MessagePattern({ cmd: 'send-proof-request' }) - async sendProofRequest(payload: { - requestProofDto: IProofRequestData; - user: IUserRequest; - }): Promise { - return this.verificationService.sendProofRequest(payload.requestProofDto); + async sendProofRequest(payload: { requestProof: IRequestProof, user: IUserRequest }): Promise { + return this.verificationService.sendProofRequest(payload.requestProof); } - /** - * Verify proof presentation - * @param proofId - * @param orgId - * @returns Verified proof presentation details - */ + /** + * Verify proof presentation + * @param proofId + * @param orgId + * @returns Verified proof presentation details + */ @MessagePattern({ cmd: 'verify-presentation' }) - async verifyPresentation(payload: { proofId: string; orgId: string; user: IUserRequest }): Promise { + async verifyPresentation(payload: { proofId: string, orgId: string, user: IUserRequest }): Promise { return this.verificationService.verifyPresentation(payload.proofId, payload.orgId); } /** - * @param orgId + * @param orgId * @returns proof presentation details */ @MessagePattern({ cmd: 'webhook-proof-presentation' }) - async webhookProofPresentation(payload: IProofPresentation): Promise { + // eslint-disable-next-line camelcase + async webhookProofPresentation(payload: IProofPresentation): Promise { return this.verificationService.webhookProofPresentation(payload); } @MessagePattern({ cmd: 'send-out-of-band-proof-request' }) - async sendOutOfBandPresentationRequest(payload: { - outOfBandRequestProof: ISendProofRequestPayload; - user: IUserRequest; - }): Promise { + async sendOutOfBandPresentationRequest(payload: { outOfBandRequestProof: ISendProofRequestPayload, user: IUserRequest }): Promise { return this.verificationService.sendOutOfBandPresentationRequest(payload.outOfBandRequestProof, payload.user); } @MessagePattern({ cmd: 'get-verified-proof-details' }) - async getVerifiedProofdetails(payload: IProofPresentationData): Promise { - const { proofId, orgId } = payload; + async getVerifiedProofdetails(payload: IProofPresentationData): Promise { + const { proofId, orgId } = payload; return this.verificationService.getVerifiedProofdetails(proofId, orgId); } @MessagePattern({ cmd: 'delete-verification-records' }) - async deleteVerificationRecord(payload: { orgId: string; userDetails: user }): Promise { + async deleteVerificationRecord(payload: {orgId: string, userDetails: user}): Promise { const { orgId, userDetails } = payload; return this.verificationService.deleteVerificationRecords(orgId, userDetails); } diff --git a/apps/verification/src/verification.module.ts b/apps/verification/src/verification.module.ts index d5e849965..7444a03e6 100644 --- a/apps/verification/src/verification.module.ts +++ b/apps/verification/src/verification.module.ts @@ -11,13 +11,7 @@ import { EmailDto } from '@credebl/common/dtos/email.dto'; import { CacheModule } from '@nestjs/cache-manager'; import { UserActivityService } from '@credebl/user-activity'; import { UserActivityRepository } from 'libs/user-activity/repositories'; -import { CommonConstants, MICRO_SERVICE_NAME } from '@credebl/common/common.constant'; -import { ConfigModule as PlatformConfig } from '@credebl/config/config.module'; -import { ContextInterceptorModule } from '@credebl/context/contextInterceptorModule'; -import { LoggerModule } from '@credebl/logger/logger.module'; -import { GlobalConfigModule } from '@credebl/config/global-config.module'; -import { NATSClient } from '@credebl/common/NATSClient'; - +import { CommonConstants } from '@credebl/common/common.constant'; @Module({ imports: [ ClientsModule.register([ @@ -29,18 +23,10 @@ import { NATSClient } from '@credebl/common/NATSClient'; } ]), - GlobalConfigModule, - CommonModule, LoggerModule, PlatformConfig, ContextInterceptorModule, + CommonModule, CacheModule.register() ], controllers: [VerificationController], - providers: [ - VerificationService, VerificationRepository, PrismaService, UserActivityService, - UserActivityRepository, Logger, OutOfBandVerification, EmailDto, NATSClient, - { - provide: MICRO_SERVICE_NAME, - useValue: 'Verification-Service' - } -] + providers: [VerificationService, VerificationRepository, PrismaService, UserActivityService, UserActivityRepository, Logger, OutOfBandVerification, EmailDto] }) export class VerificationModule { } diff --git a/apps/verification/src/verification.service.ts b/apps/verification/src/verification.service.ts index 1a4cb77d1..8728dbcb7 100644 --- a/apps/verification/src/verification.service.ts +++ b/apps/verification/src/verification.service.ts @@ -1,62 +1,30 @@ /* eslint-disable camelcase */ -import { - BadRequestException, - HttpException, - Inject, - Injectable, - InternalServerErrorException, - Logger, - NotFoundException -} from '@nestjs/common'; +import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; import { ClientProxy, RpcException } from '@nestjs/microservices'; import { map } from 'rxjs/operators'; -import { - IGetAllProofPresentations, - IProofRequestSearchCriteria, - IGetProofPresentationById, - IProofPresentation, - IProofRequestPayload, - IRequestProof, - ISendProofRequestPayload, - IVerifyPresentation, - IVerifiedProofData, - IInvitation, - IProofRequestData, - IEmailResponse -} from './interfaces/verification.interface'; +import { IGetAllProofPresentations, IProofRequestSearchCriteria, IGetProofPresentationById, IProofPresentation, IProofRequestPayload, IRequestProof, ISendProofRequestPayload, IVerifyPresentation, IVerifiedProofData, IInvitation } from './interfaces/verification.interface'; import { VerificationRepository } from './repositories/verification.repository'; import { ATTRIBUTE_NAME_REGEX, CommonConstants } from '@credebl/common/common.constant'; -import { RecordType, agent_invitations, org_agents, organisation, presentations, user } from '@prisma/client'; -import { AutoAccept, VerificationProcessState } from '@credebl/enum/enum'; +import { RecordType, agent_invitations, org_agents, organisation, user } from '@prisma/client'; +import { AutoAccept, OrgAgentType, VerificationProcessState } from '@credebl/enum/enum'; import { ResponseMessages } from '@credebl/common/response-messages'; import * as QRCode from 'qrcode'; import { OutOfBandVerification } from '../templates/out-of-band-verification.template'; import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { Cache } from 'cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; -import { - IProofPresentationDetails, - IProofPresentationList, - IVerificationRecords -} from '@credebl/common/interfaces/verification.interface'; -import { - API_Version, - ProofRequestLabel, - ProofRequestType, - ProofVersion, - ProtocolVersionType -} from 'apps/api-gateway/src/verification/enum/verification.enum'; +import { IProofPresentationDetails, IProofPresentationList, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; +import { ProofRequestType } from 'apps/api-gateway/src/verification/enum/verification.enum'; import { UserActivityService } from '@credebl/user-activity'; -import { convertUrlToDeepLinkUrl, getAgentUrl } from '@credebl/common/common.utils'; +import { convertUrlToDeepLinkUrl } from '@credebl/common/common.utils'; import { UserActivityRepository } from 'libs/user-activity/repositories'; import { ISchemaDetail } from '@credebl/common/interfaces/schema.interface'; -import { NATSClient } from '@credebl/common/NATSClient'; -import { from } from 'rxjs'; -import { EmailService } from '@credebl/common/email.service'; @Injectable() export class VerificationService { + private readonly logger = new Logger('VerificationService'); constructor( @@ -64,19 +32,16 @@ export class VerificationService { private readonly verificationRepository: VerificationRepository, private readonly userActivityRepository: UserActivityRepository, private readonly outOfBandVerification: OutOfBandVerification, - // TODO: Remove duplicate, unused variable private readonly userActivityService: UserActivityService, private readonly emailData: EmailDto, - // TODO: Remove duplicate, unused variable - @Inject(CACHE_MANAGER) private readonly cacheService: Cache, - private readonly natsClient: NATSClient, - private readonly emailService: EmailService - ) {} + @Inject(CACHE_MANAGER) private cacheService: Cache + + ) { } /** * Get all proof presentations - * @param user - * @param orgId + * @param user + * @param orgId * @returns Get all proof presentation */ @@ -91,10 +56,11 @@ export class VerificationService { orgId, proofRequestsSearchCriteria ); + const schemaIds = getProofRequestsList?.proofRequestsList?.map((schema) => schema?.schemaId).filter(Boolean); const getSchemaDetails = await this._getSchemaAndOrganizationDetails(schemaIds); - + const proofDetails = getProofRequestsList.proofRequestsList.map((proofRequest) => { const schemaDetail = getSchemaDetails.find((schema) => schema.schemaLedgerId === proofRequest.schemaId); @@ -128,8 +94,7 @@ export class VerificationService { } = { totalItems: getProofRequestsList.proofRequestsCount, hasNextPage: - proofRequestsSearchCriteria.pageSize * proofRequestsSearchCriteria.pageNumber < - getProofRequestsList.proofRequestsCount, + proofRequestsSearchCriteria.pageSize * proofRequestsSearchCriteria.pageNumber < getProofRequestsList.proofRequestsCount, hasPreviousPage: 1 < proofRequestsSearchCriteria.pageNumber, nextPage: Number(proofRequestsSearchCriteria.pageNumber) + 1, previousPage: proofRequestsSearchCriteria.pageNumber - 1, @@ -139,6 +104,7 @@ export class VerificationService { return proofPresentationsResponse; } catch (error) { + this.logger.error( `[getProofRequests] [NATS call]- error in fetch proof requests details : ${JSON.stringify(error)}` ); @@ -152,9 +118,9 @@ export class VerificationService { const payload = { templateIds }; - const schemaAndOrgDetails = await this.natsClient - .send(this.verificationServiceProxy, pattern, payload) - + const schemaAndOrgDetails = await this.verificationServiceProxy + .send(pattern, payload) + .toPromise() .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -172,6 +138,7 @@ export class VerificationService { try { return await this.verificationRepository.getVerificationRecordsCount(orgId); } catch (error) { + this.logger.error( `[getVerificationRecords ] [NATS call]- error in get verification records count : ${JSON.stringify(error)}` ); @@ -179,9 +146,10 @@ export class VerificationService { } } + /** * Consume agent API for get all proof presentations - * @param payload + * @param payload * @returns Get all proof presentation */ async _getProofPresentations(payload: IGetAllProofPresentations): Promise<{ @@ -196,195 +164,136 @@ export class VerificationService { } } - /** - * Get verified proof presentation by issuerId - * @param issuerId - * @returns Proof presentation details by issuerId - */ - async getProofPresentationByIssuerId(issuerId: string): Promise { - try { - const orgDetails = await this.verificationRepository.getOrgDetails(issuerId); - - const getVerifiedProofCount = await this.verificationRepository.getVerificationRecordsCount( - orgDetails?.['orgId'], - VerificationProcessState.DONE - ); - - return getVerifiedProofCount; - } catch (error) { - this.logger.error( - `[getProofPresentationByIssuerId] - error in get proof presentation by issuerId : ${JSON.stringify(error)}` - ); - throw new RpcException(error.response ? error.response : error); - } - } - /** * Get proof presentation by proofId - * @param proofId - * @param orgId + * @param proofId + * @param orgId * @returns Proof presentation details by proofId */ async getProofPresentationById(proofId: string, orgId: string): Promise { try { const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); - const url = await getAgentUrl( - getAgentDetails?.agentEndPoint, - CommonConstants.URL_GET_PROOF_PRESENTATION_BY_ID_FLAG, - proofId - ); + const verificationMethodLabel = 'get-proof-presentation-by-id'; + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId, '', proofId); + const payload = { orgId, url }; const getProofPresentationById = await this._getProofPresentationById(payload); return getProofPresentationById?.response; } catch (error) { - this.logger.error( - `[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}` - ); - const errorMessage = error?.response?.error?.reason || error?.message; + this.logger.error(`[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}`); + const errorStack = error?.response?.error?.reason; - if (errorMessage?.includes('not found')) { - throw new NotFoundException(errorMessage); + if (errorStack) { + throw new RpcException({ + message: ResponseMessages.verification.error.proofNotFound, + statusCode: error?.response?.status, + error: errorStack + }); + } else { + throw new RpcException(error.response ? error.response : error); } - - throw new RpcException(error.response ? error.response : error); } } /** * Consume agent API for get proof presentation by id - * @param payload + * @param payload * @returns Get proof presentation details */ async _getProofPresentationById(payload: IGetProofPresentationById): Promise<{ response: string; }> { try { + const pattern = { cmd: 'agent-get-proof-presentation-by-id' }; return await this.natsCall(pattern, payload); } catch (error) { - this.logger.error( - `[_getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}` - ); + this.logger.error(`[_getProofPresentationById] - error in get proof presentation by id : ${JSON.stringify(error)}`); throw error; } } /** * Send proof request - * @param orgId + * @param orgId * @returns Requested proof presentation details */ - - async sendProofRequest(requestProof: IProofRequestData): Promise { + async sendProofRequest(requestProof: ISendProofRequestPayload): Promise { try { const comment = requestProof.comment ? requestProof.comment : ''; - const getAgentDetails = await this.verificationRepository.getAgentEndPoint(requestProof.orgId); - const url = await getAgentUrl(getAgentDetails?.agentEndPoint, CommonConstants.REQUEST_PROOF); - // Function to create a payload - const createPayload = async (connectionId: string): Promise => { - const proofRequestPayload = { - comment, - connectionId, - autoAcceptProof: requestProof.autoAcceptProof || AutoAccept.Never, - goalCode: requestProof.goalCode || undefined, - parentThreadId: requestProof.parentThreadId || undefined, - willConfirm: requestProof.willConfirm || undefined - }; - - const payload: IProofRequestPayload = { - orgId: requestProof.orgId, - url, - proofRequestPayload: {} - }; - if (requestProof.type === ProofRequestType.INDY) { - const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload( - requestProof as IRequestProof - ); - payload.proofRequestPayload = { - protocolVersion: requestProof.protocolVersion || ProtocolVersionType.PROTOCOL_VERSION_1, - proofFormats: { - indy: { - name: ProofRequestLabel.PROOF_REQUEST, - version: ProofVersion.PROOF_VERSION, - requested_attributes: requestedAttributes, - requested_predicates: requestedPredicates - } - }, - ...proofRequestPayload - }; - } else if (requestProof.type === ProofRequestType.PRESENTATIONEXCHANGE) { - payload.proofRequestPayload = { - protocolVersion: requestProof.protocolVersion || ProtocolVersionType.PROTOCOL_VERSION_2, - proofFormats: { - presentationExchange: { - presentationDefinition: requestProof.presentationDefinition - } - }, - ...proofRequestPayload - }; - } + const getAgentDetails = await this.verificationRepository.getAgentEndPoint(requestProof.orgId); - return payload; + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + const verificationMethodLabel = 'request-proof'; + const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); + + const payload: IProofRequestPayload = { + orgId: requestProof.orgId, + url, + proofRequestPayload: {} }; - if (API_Version.VERSION_1 === requestProof.version) { - const connectionIds = Array.isArray(requestProof.connectionId) - ? requestProof.connectionId - : [requestProof.connectionId]; + const proofRequestPayload = { + comment, + connectionId: requestProof.connectionId, + autoAcceptProof: requestProof.autoAcceptProof ? requestProof.autoAcceptProof : AutoAccept.Never, + goalCode: requestProof.goalCode || undefined, + parentThreadId: requestProof.parentThreadId || undefined, + willConfirm: requestProof.willConfirm || undefined + }; - const responses: string[] = []; - for (const connectionId of connectionIds) { - const payload = await createPayload(connectionId); - const getProofPresentationById = await this._sendProofRequest(payload); - responses.push(getProofPresentationById?.response); - } - return responses; - } else { - const connectionId = Array.isArray(requestProof.connectionId) - ? requestProof.connectionId[0] - : requestProof.connectionId; + if (requestProof.type === ProofRequestType.INDY) { + const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(requestProof as IRequestProof); + payload.proofRequestPayload = { + protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v1', + proofFormats: { + indy: { + name: 'Proof Request', + version: '1.0', + requested_attributes: requestedAttributes, + requested_predicates: requestedPredicates + } + }, + ...proofRequestPayload + }; - const payload = await createPayload(connectionId); - const getProofPresentationById = await this._sendProofRequest(payload); - return getProofPresentationById?.response; - } + } else if (requestProof.type === ProofRequestType.PRESENTATIONEXCHANGE) { + payload.proofRequestPayload = { + protocolVersion: requestProof.protocolVersion ? requestProof.protocolVersion : 'v2', + proofFormats: { + presentationExchange: { + presentationDefinition: requestProof.presentationDefinition + } + }, + ...proofRequestPayload + }; + } + const getProofPresentationById = await this._sendProofRequest(payload); + return getProofPresentationById?.response; } catch (error) { - // Handle cases where identical attributes are used in both predicates and non-predicates. - // This case is not supported in credo-ts, so we are handling in platform - // TODO: Handle all credo-errors globally in platform - - const errorMessage = error?.status?.message?.error?.message; - const match = errorMessage?.match( - /CredoError: The proof request contains duplicate predicates and attributes: (.+)/ - ); - if (match) { - const [, duplicateAttributes] = match; - throw new RpcException({ - message: `CredoError: The proof request contains duplicate attributes: ${duplicateAttributes}`, - statusCode: 500 - }); - } - - this.logger.error(`[sendProofRequest] - error in sending proof request: ${JSON.stringify(error)}`); + this.logger.error(`[verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); this.verificationErrorHandling(error); + } } /** * Consume agent API for request proof presentation - * @param orgId + * @param orgId * @returns Get requested proof presentation details */ async _sendProofRequest(payload: IProofRequestPayload): Promise<{ response: string; }> { try { + const pattern = { cmd: 'agent-send-proof-request' }; @@ -398,22 +307,24 @@ export class VerificationService { /** * Verify proof presentation - * @param proofId - * @param orgId + * @param proofId + * @param orgId * @returns Verified proof presentation details */ async verifyPresentation(proofId: string, orgId: string): Promise { try { const getAgentData = await this.verificationRepository.getAgentEndPoint(orgId); - const url = await getAgentUrl(getAgentData?.agentEndPoint, CommonConstants.ACCEPT_PRESENTATION, proofId); - + const orgAgentTypeData = await this.verificationRepository.getOrgAgentType(getAgentData?.orgAgentTypeId); + + const verificationMethod = 'accept-presentation'; + + const url = await this.getAgentUrl(verificationMethod, orgAgentTypeData, getAgentData?.agentEndPoint, getAgentData?.tenantId, '', proofId); + const payload = { orgId, url }; const getProofPresentationById = await this._verifyPresentation(payload); return getProofPresentationById?.response; } catch (error) { - this.logger.error( - `[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}` - ); + this.logger.error(`[getProofPresentationById] - error in get proof presentation by proofId : ${JSON.stringify(error)}`); const errorStack = error?.response?.error?.reason; if (errorStack) { @@ -430,28 +341,31 @@ export class VerificationService { /** * Consume agent API for verify proof presentation - * @param payload + * @param payload * @returns Get verified proof presentation details */ async _verifyPresentation(payload: IVerifyPresentation): Promise<{ response: string; }> { try { + const pattern = { cmd: 'agent-verify-presentation' }; return await this.natsCall(pattern, payload); + } catch (error) { this.logger.error(`[_verifyPresentation] - error in verify presentation : ${JSON.stringify(error)}`); throw error; } } - async webhookProofPresentation(proofPresentationPayload: IProofPresentation): Promise { + async webhookProofPresentation(proofPresentationPayload: IProofPresentation): Promise { try { const proofPresentation = await this.verificationRepository.storeProofPresentation(proofPresentationPayload); return proofPresentation; + } catch (error) { this.logger.error(`[webhookProofPresentation] - error in webhook proof presentation : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -459,17 +373,15 @@ export class VerificationService { } /** - * Request out-of-band proof presentation - * @param outOfBandRequestProof - * @returns Get requested proof presentation details - */ - async sendOutOfBandPresentationRequest( - outOfBandRequestProof: ISendProofRequestPayload, - user: IUserRequest - ): Promise { + * Request out-of-band proof presentation + * @param outOfBandRequestProof + * @returns Get requested proof presentation details + */ + async sendOutOfBandPresentationRequest(outOfBandRequestProof: ISendProofRequestPayload, user: IUserRequest): Promise { try { - // const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(outOfBandRequestProof); + // const { requestedAttributes, requestedPredicates } = await this._proofRequestPayload(outOfBandRequestProof); + const [getAgentDetails, getOrganization] = await Promise.all([ this.verificationRepository.getAgentEndPoint(user.orgId), this.verificationRepository.getOrganization(user.orgId) @@ -480,50 +392,47 @@ export class VerificationService { if (getOrganization?.logoUrl) { outOfBandRequestProof['imageUrl'] = getOrganization?.logoUrl; } - + outOfBandRequestProof['label'] = label; - const url = await getAgentUrl( - getAgentDetails?.agentEndPoint, - CommonConstants.CREATE_OUT_OF_BAND_PROOF_PRESENTATION - ); + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + const verificationMethodLabel = 'create-request-out-of-band'; + const url = await this.getAgentUrl(verificationMethodLabel, orgAgentType, getAgentDetails?.agentEndPoint, getAgentDetails?.tenantId); + // Destructuring 'outOfBandRequestProof' to remove emailId, as it is not used while agent operation const { isShortenUrl, emailId, type, reuseConnection, ...updateOutOfBandRequestProof } = outOfBandRequestProof; let invitationDid: string | undefined; if (true === reuseConnection) { - const data: agent_invitations[] = await this.verificationRepository.getInvitationDidByOrgId(user.orgId); - if (data && 0 < data.length) { - const [firstElement] = data; - invitationDid = firstElement?.invitationDid ?? undefined; - } + const invitation: agent_invitations = await this.verificationRepository.getInvitationDidByOrgId(user.orgId); + invitationDid = invitation?.invitationDid ?? undefined; } outOfBandRequestProof.autoAcceptProof = outOfBandRequestProof.autoAcceptProof || AutoAccept.Always; + let payload: IProofRequestPayload; if (ProofRequestType.INDY === type) { updateOutOfBandRequestProof.protocolVersion = updateOutOfBandRequestProof.protocolVersion || 'v1'; updateOutOfBandRequestProof.invitationDid = invitationDid || undefined; updateOutOfBandRequestProof.imageUrl = getOrganization?.logoUrl || undefined; - payload = { - orgId: user.orgId, - url, - proofRequestPayload: updateOutOfBandRequestProof - }; + payload = { + orgId: user.orgId, + url, + proofRequestPayload: updateOutOfBandRequestProof + }; } - + if (ProofRequestType.PRESENTATIONEXCHANGE === type) { - payload = { + + payload = { orgId: user.orgId, url, proofRequestPayload: { goalCode: outOfBandRequestProof.goalCode, - // TODO: [Credo-ts] Issue with parentThreadId in creating an OOB proof request. - // This causes failures in OOB connection establishment. - // parentThreadId: outOfBandRequestProof?.parentThreadId, - protocolVersion: outOfBandRequestProof.protocolVersion || 'v2', - comment: outOfBandRequestProof.comment, + parentThreadId: outOfBandRequestProof.parentThreadId, + protocolVersion:outOfBandRequestProof.protocolVersion || 'v2', + comment:outOfBandRequestProof.comment, label, imageUrl: outOfBandRequestProof?.imageUrl, proofFormats: { @@ -531,19 +440,19 @@ export class VerificationService { presentationDefinition: { id: outOfBandRequestProof.presentationDefinition.id, name: outOfBandRequestProof.presentationDefinition.name, - purpose: outOfBandRequestProof?.presentationDefinition?.purpose, + purpose: outOfBandRequestProof.presentationDefinition.purpose, input_descriptors: [...outOfBandRequestProof.presentationDefinition.input_descriptors] } } }, - autoAcceptProof: outOfBandRequestProof.autoAcceptProof, - invitationDid: invitationDid || undefined + autoAcceptProof:outOfBandRequestProof.autoAcceptProof, + invitationDid:invitationDid || undefined } - }; + }; } + if (emailId) { const emailResponse = await this.sendEmailInBatches(payload, emailId, getAgentDetails, getOrganization); - await this.verificationRepository.saveEmail(emailResponse as IEmailResponse[]); return emailResponse; } else { const presentationProof: IInvitation = await this.generateOOBProofReq(payload); @@ -560,7 +469,7 @@ export class VerificationService { throw new Error(ResponseMessages.verification.error.proofPresentationNotFound); } return presentationProof; - } + } } catch (error) { this.logger.error(`[sendOutOfBandPresentationRequest] - error in out of band proof request : ${error.message}`); this.verificationErrorHandling(error); @@ -575,6 +484,7 @@ export class VerificationService { return message.response; } + private async generateOOBProofReq(payload: IProofRequestPayload): Promise { const getProofPresentation = await this._sendOutOfBandProofRequest(payload); @@ -584,49 +494,43 @@ export class VerificationService { return getProofPresentation.response; } + // Currently batch size is not used, as length of emails sent is restricted to '10' - async sendEmailInBatches( - payload: IProofRequestPayload, - emailIds: string[], - getAgentDetails: org_agents, - organizationDetails: organisation - ): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async sendEmailInBatches(payload: IProofRequestPayload, emailIds: string[], getAgentDetails: org_agents, organizationDetails: organisation): Promise { try { - const accumulatedErrors = []; - const accumulatedResponse = []; - - for (const email of emailIds) { - try { - const response = await this.sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails); - accumulatedResponse.push({ email, ...response }); - - await this.delay(500); - } catch (error) { - this.logger.error(`Error sending email to ${email}::::::`, error); - accumulatedErrors.push(error); + const accumulatedErrors = []; + const accumulatedResponse = []; + + for (const email of emailIds) { + try { + const response = await this.sendOutOfBandProofRequest(payload, email, getAgentDetails, organizationDetails); + accumulatedResponse.push({email, ...response}); + await this.delay(500); + } catch (error) { + this.logger.error(`Error sending email to ${email}::::::`, error); + accumulatedErrors.push(error); + } } - } - if (0 < accumulatedErrors.length) { - this.logger.error(accumulatedErrors); - throw new Error(ResponseMessages.verification.error.emailSend); - } - - return accumulatedResponse; - } catch (error) { - this.logger.error('[sendEmailInBatches] - error in sending email in batches'); - throw new Error(ResponseMessages.verification.error.batchEmailSend); + if (0 < accumulatedErrors.length) { + this.logger.error(accumulatedErrors); + throw new Error(ResponseMessages.verification.error.emailSend); } + + return accumulatedResponse; + + } catch (error) { + this.logger.error('[sendEmailInBatches] - error in sending email in batches'); + throw new Error(ResponseMessages.verification.error.batchEmailSend); } + } + // This function is specifically for OOB verification using email - async sendOutOfBandProofRequest( - payload: IProofRequestPayload, - email: string, - getAgentDetails: org_agents, - organizationDetails: organisation - ): Promise { - const getProofPresentation = await this._sendOutOfBandProofRequest(payload); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async sendOutOfBandProofRequest(payload: IProofRequestPayload, email: string, getAgentDetails: org_agents, organizationDetails: organisation): Promise { + const getProofPresentation = await this._sendOutOfBandProofRequest(payload); if (!getProofPresentation) { throw new Error(ResponseMessages.verification.error.proofPresentationNotFound); @@ -649,11 +553,7 @@ export class VerificationService { this.emailData.emailFrom = platformConfigData.emailFrom; this.emailData.emailTo = email; this.emailData.emailSubject = `${process.env.PLATFORM_NAME} Platform: Verification of Your Credentials`; - this.emailData.emailHtml = await this.outOfBandVerification.outOfBandVerification( - email, - organizationDetails.name, - deepLinkURL - ); + this.emailData.emailHtml = await this.outOfBandVerification.outOfBandVerification(email, organizationDetails.name, deepLinkURL); this.emailData.emailAttachments = [ { filename: 'qrcode.png', @@ -662,89 +562,87 @@ export class VerificationService { disposition: 'attachment' } ]; - const isEmailSent = await this.emailService.sendEmail(this.emailData); + const isEmailSent = await sendEmail(this.emailData); if (!isEmailSent) { throw new Error(ResponseMessages.verification.error.emailSend); } return { - isEmailSent, - outOfBandRecordId: getProofPresentation?.response?.outOfBandRecord?.id, - proofRecordThId: getProofPresentation?.response?.proofRecordThId - }; + isEmailSent, + outOfBandRecordId: getProofPresentation?.response?.outOfBandRecord?.id, + proofRecordThId: getProofPresentation?.response?.proofRecordThId + }; } + /** * Consume agent API for request out-of-band proof presentation - * @param payload + * @param payload * @returns Get requested proof presentation details */ async _sendOutOfBandProofRequest(payload: IProofRequestPayload): Promise<{ response; }> { try { + const pattern = { cmd: 'agent-send-out-of-band-proof-request' }; + this.logger.log(`_sendOutOfBandProofRequest: nats call payload: ${JSON.stringify(payload)}`); return await this.natsCall(pattern, payload); + } catch (error) { this.logger.error(`[_sendOutOfBandProofRequest] - error in Out Of Band Presentation : ${JSON.stringify(error)}`); throw error; } } - async _proofRequestPayload(proofRequestpayload: IProofRequestData): Promise<{ + async _proofRequestPayload(proofRequestpayload: IRequestProof): Promise<{ requestedAttributes; requestedPredicates; }> { try { - let requestedAttributes = {}; + let requestedAttributes = {}; const requestedPredicates = {}; - - const indyAttributes = proofRequestpayload.proofFormats?.indy; - if (indyAttributes && indyAttributes.attributes) { - requestedAttributes = Object.fromEntries( - indyAttributes.attributes.map((attribute, index) => { - const attributeElement = attribute.attributeName || attribute.attributeNames; - const attributeReferent = `additionalProp${index + 1}`; - const attributeKey = attribute.attributeName ? 'name' : 'names'; - - if (!attribute.condition && !attribute.value) { - return [ - attributeReferent, - { - [attributeKey]: attributeElement, - restrictions: [ - { - cred_def_id: proofRequestpayload?.proofFormats?.indy?.attributes[index].credDefId - ? proofRequestpayload.proofFormats.indy.attributes[index].credDefId - : undefined, - schema_id: proofRequestpayload?.proofFormats?.indy?.attributes[index].schemaId - } - ] - } - ]; - } else { - requestedPredicates[attributeReferent] = { - p_type: attribute.condition, - name: attributeElement, - p_value: parseInt(attribute.value), + const { attributes } = proofRequestpayload; + if (attributes) { + requestedAttributes = Object.fromEntries(attributes.map((attribute, index) => { + const attributeElement = attribute.attributeName || attribute.attributeNames; + const attributeReferent = `additionalProp${index + 1}`; + const attributeKey = attribute.attributeName ? 'name' : 'names'; + + if (!attribute.condition && !attribute.value) { + + return [ + attributeReferent, + { + [attributeKey]: attributeElement, restrictions: [ { - cred_def_id: proofRequestpayload?.proofFormats?.indy?.attributes[index].credDefId - ? proofRequestpayload?.proofFormats?.indy?.attributes[index].credDefId - : undefined, - schema_id: proofRequestpayload?.proofFormats?.indy?.attributes[index].schemaId + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, + schema_id: proofRequestpayload.attributes[index].schemaId } ] - }; - } + } + ]; + } else { + requestedPredicates[attributeReferent] = { + p_type: attribute.condition, + name: attributeElement, + p_value: parseInt(attribute.value), + restrictions: [ + { + cred_def_id: proofRequestpayload.attributes[index].credDefId ? proofRequestpayload.attributes[index].credDefId : undefined, + schema_id: proofRequestpayload.attributes[index].schemaId + } + ] + }; + } - return [attributeReferent]; - }) - ); + return [attributeReferent]; + })); return { requestedAttributes, @@ -756,16 +654,119 @@ export class VerificationService { } catch (error) { this.logger.error(`[proofRequestPayload] - error in proof request payload : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); + + } + } + + /** + * Description: Fetch agent url + * @param referenceId + * @returns agent URL + */ + async getAgentUrl( + verificationMethodLabel: string, + orgAgentType: string, + agentEndPoint: string, + tenantId: string, + threadId?: string, + proofPresentationId?: string + ): Promise { + try { + + + let url; + switch (verificationMethodLabel) { + case 'get-proof-presentation': { + url = orgAgentType === OrgAgentType.DEDICATED && threadId + ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATIONS}?threadId=${threadId}` + : orgAgentType === OrgAgentType.SHARED && threadId + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}?threadId=${threadId}`.replace('#', tenantId) + : orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATIONS}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS}`.replace('#', tenantId) + : null; + break; + } + + case 'get-proof-presentation-by-id': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_GET_PROOF_PRESENTATION_BY_ID}`.replace('#', proofPresentationId) + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_GET_PROOFS_BY_PRESENTATION_ID}`.replace('#', proofPresentationId).replace('@', tenantId) + : null; + break; + } + + case 'request-proof': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_SEND_PROOF_REQUEST}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_REQUEST_PROOF}`.replace('#', tenantId) + : null; + break; + } + + case 'accept-presentation': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_VERIFY_PRESENTATION}`.replace('#', proofPresentationId) + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_ACCEPT_PRESENTATION}`.replace('@', proofPresentationId).replace('#', tenantId) + : null; + break; + } + + case 'create-request-out-of-band': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_SEND_OUT_OF_BAND_CREATE_REQUEST}` + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_OUT_OF_BAND_CREATE_REQUEST}`.replace('#', tenantId) + : null; + break; + } + + case 'get-verified-proof': { + url = orgAgentType === OrgAgentType.DEDICATED + ? `${agentEndPoint}${CommonConstants.URL_PROOF_FORM_DATA}`.replace('#', proofPresentationId) + : orgAgentType === OrgAgentType.SHARED + ? `${agentEndPoint}${CommonConstants.URL_SHAGENT_PROOF_FORM_DATA}`.replace('@', proofPresentationId).replace('#', tenantId) + : null; + break; + } + + default: { + break; + } + } + + if (!url) { + throw new NotFoundException(ResponseMessages.verification.error.agentUrlNotFound); + } + + return url; + } catch (error) { + this.logger.error(`Error in get agent url: ${JSON.stringify(error)}`); + throw error; + } } async getVerifiedProofdetails(proofId: string, orgId: string): Promise { try { const getAgentDetails = await this.verificationRepository.getAgentEndPoint(orgId); + const verificationMethodLabel = 'get-verified-proof'; let credDefId; let schemaId; let certificate; - const url = await getAgentUrl(getAgentDetails?.agentEndPoint, CommonConstants.GET_VERIFIED_PROOF, proofId); + const orgAgentType = await this.verificationRepository.getOrgAgentType(getAgentDetails?.orgAgentTypeId); + const url = await this.getAgentUrl( + verificationMethodLabel, + orgAgentType, + getAgentDetails?.agentEndPoint, + getAgentDetails?.tenantId, + '', + proofId + ); const payload = { orgId, url }; @@ -798,7 +799,7 @@ export class VerificationService { getProofPresentationById?.response?.presentation?.presentationExchange?.verifiableCredential[0].prettyVc ?.certificate; } - + if ( requestedAttributesForPresentationExchangeFormat && Array.isArray(requestedAttributesForPresentationExchangeFormat) @@ -934,9 +935,11 @@ export class VerificationService { let schemaId; if (attribute?.restrictions) { + credDefId = attribute?.restrictions[0]?.cred_def_id; schemaId = attribute?.restrictions[0]?.schema_id; } else if (getProofPresentationById?.response?.presentation?.indy?.identifiers) { + credDefId = getProofPresentationById?.response?.presentation?.indy?.identifiers[0].cred_def_id; schemaId = getProofPresentationById?.response?.presentation?.indy?.identifiers[0].schema_id; } @@ -948,6 +951,7 @@ export class VerificationService { response; }> { try { + //nats call in agent for fetch verified proof details const pattern = { cmd: 'get-agent-verified-proof-details' @@ -960,23 +964,21 @@ export class VerificationService { } } + async _getOrgAgentApiKey(orgId: string): Promise { const pattern = { cmd: 'get-org-agent-api-key' }; const payload = { orgId }; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const message = await this.natsClient.send(this.verificationServiceProxy, pattern, payload); + const message = await this.verificationServiceProxy.send(pattern, payload).toPromise(); return message; } catch (error) { this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); + throw new HttpException({ + status: error.status, + error: error.message + }, error.status); } } @@ -984,43 +986,43 @@ export class VerificationService { if (!error && !error?.status && !error?.status?.message && !error?.status?.message?.error) { throw new RpcException(error.response ? error.response : error); } else { + if (error?.message) { + throw new RpcException({ + message: error?.message, + statusCode: HttpStatus.INTERNAL_SERVER_ERROR + }); + } throw new RpcException({ - message: error?.status?.message?.error?.reason - ? error?.status?.message?.error?.reason - : error?.status?.message?.error, + message: error?.status?.message?.error?.reason ? error?.status?.message?.error?.reason : error?.status?.message?.error, statusCode: error?.status?.code }); } } - async natsCall( - pattern: object, - payload: object - ): Promise<{ + async natsCall(pattern: object, payload: object): Promise<{ response: string; }> { - return from(this.natsClient.send(this.verificationServiceProxy, pattern, payload)) - .pipe( - map((response) => ({ - response - })) - ) - .toPromise() - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.statusCode, - error: error.error, - message: error.message - }, - error.error - ); - }); - } - + return this.verificationServiceProxy + .send(pattern, payload) + .pipe( + map((response) => ( + { + response + })) + ) + .toPromise() + .catch(error => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException({ + status: error.statusCode, + error: error.error, + message: error.message + }, error.error); + }); + } + async delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } async deleteVerificationRecords(orgId: string, userDetails: user): Promise { @@ -1029,9 +1031,9 @@ export class VerificationService { if (0 === deleteProofRecords?.deleteResult?.count) { throw new NotFoundException(ResponseMessages.verification.error.verificationRecordsNotFound); - } + } - const statusCounts = { + const statusCounts = { [VerificationProcessState.PROPOSAL_SENT]: 0, [VerificationProcessState.PROPOSAL_RECEIVED]: 0, [VerificationProcessState.REQUEST_SENT]: 0, @@ -1041,34 +1043,27 @@ export class VerificationService { [VerificationProcessState.DONE]: 0, [VerificationProcessState.DECLIEND]: 0, [VerificationProcessState.ABANDONED]: 0 - }; - - await Promise.all( - deleteProofRecords.recordsToDelete.map(async (record) => { - statusCounts[record.state]++; - }) - ); + }; - const filteredStatusCounts = Object.fromEntries(Object.entries(statusCounts).filter((entry) => 0 < entry[1])); + await Promise.all(deleteProofRecords.recordsToDelete.map(async (record) => { + statusCounts[record.state]++; + })); + const filteredStatusCounts = Object.fromEntries( + Object.entries(statusCounts).filter(entry => 0 < entry[1]) + ); + const deletedVerificationData = { - deletedProofRecordsCount: deleteProofRecords?.deleteResult?.count, - deletedRecordsStatusCount: filteredStatusCounts - }; + deletedProofRecordsCount : deleteProofRecords?.deleteResult?.count, + deletedRecordsStatusCount : filteredStatusCounts + }; - await this.userActivityRepository._orgDeletedActivity( - orgId, - userDetails, - deletedVerificationData, - RecordType.VERIFICATION_RECORD - ); + await this.userActivityRepository._orgDeletedActivity(orgId, userDetails, deletedVerificationData, RecordType.VERIFICATION_RECORD); - return deleteProofRecords; + return deleteProofRecords; } catch (error) { - this.logger.error( - `[deleteVerificationRecords] - error in deleting verification records: ${JSON.stringify(error)}` - ); + this.logger.error(`[deleteVerificationRecords] - error in deleting verification records: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); } } -} +} \ No newline at end of file diff --git a/apps/verification/templates/out-of-band-verification.template.ts b/apps/verification/templates/out-of-band-verification.template.ts index 46486a398..3aa4ca672 100644 --- a/apps/verification/templates/out-of-band-verification.template.ts +++ b/apps/verification/templates/out-of-band-verification.template.ts @@ -44,28 +44,22 @@ export class OutOfBandVerification { iOS App Store. (Skip, if already downloaded)
  • Complete the onboarding process in ${process.env.MOBILE_APP}.
  • -
  • Open the “Share Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP})
  • -
  • Tap the "Send Proof" button in ${process.env.MOBILE_APP} to share you credential data.
  • +
  • Open the “Share Credential” link below in this email (This will open the link in the ${process.env.MOBILE_APP} App)
  • +
  • Tap the "Share" button in ${process.env.MOBILE_APP} to share you credential data.
  • - Note: If the above steps do not work for you, please open the attached QR Code image in this email on another device, and scan the QR code using the ${process.env.MOBILE_APP_NAME} on your mobile device. + Note: Alternatively, you will find a QR Code image attached to this email. You can open the QR code on another device and scan the QR code using the ${process.env.MOBILE_APP} App on your mobile device. The QR Code is single-use.

    -