From 009c2ec478de97f9520d27921fe5c0cfce9e4163 Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Thu, 1 Aug 2024 17:01:10 -0400 Subject: [PATCH 1/6] DockerHub username switch `amplicalabs` -> `projectlibertylabs` (#323) # Problem Rename is happening, so switching the docker hub username with this change. Closes #322 # Solution - Added new secrets to the GitHub org for the new `projectlibertylabs` - Updated references to docker ## Steps to Verify: 1. Do a release 1. No more references to `amplicalabs` Docker --- .github/workflows/release.yml | 6 +++--- docker-compose.yaml | 10 +++++----- services/content-watcher/INSTALLING.md | 8 ++++---- services/content-watcher/docker-compose.yaml | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e64fd398..a9702ef4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ on: env: FULL_TAG: ${{ github.event.release.tag_name || github.event.inputs.release-tag}} - DOCKER_HUB_PROFILE: amplicalabs + DOCKER_HUB_PROFILE: projectlibertylabs ALL_SERVICES: '["account", "content-publishing", "content-watcher", "graph"]' jobs: @@ -107,8 +107,8 @@ jobs: - name: Login to DockerHub uses: docker/login-action@v3 with: - username: ${{secrets.DOCKERHUB_USERNAME_FC}} - password: ${{secrets.DOCKERHUB_TOKEN_FC}} + username: ${{secrets.DOCKERHUB_USERNAME}} + password: ${{secrets.DOCKERHUB_TOKEN}} - name: Build and Push account-service-service Image uses: docker/build-push-action@v5 with: diff --git a/docker-compose.yaml b/docker-compose.yaml index d01d70b5..3b309122 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -42,7 +42,7 @@ services: - ipfs_data:/data/ipfs content-publishing-service-api: - image: amplicalabs/content-publishing-service:latest + image: projectlibertylabs/content-publishing-service:latest platform: linux/amd64 ports: - 3001:3000 @@ -61,7 +61,7 @@ services: - amplica-gateway content-publishing-service-worker: - image: amplicalabs/content-publishing-service:latest + image: projectlibertylabs/content-publishing-service:latest platform: linux/amd64 env_file: - path: .env.common @@ -78,7 +78,7 @@ services: - amplica-gateway graph-service: - image: amplicalabs/graph-service:latest + image: projectlibertylabs/graph-service:latest platform: linux/amd64 ports: - 3002:3000 @@ -94,7 +94,7 @@ services: - amplica-gateway account-service-api: - image: amplicalabs/account-service:latest + image: projectlibertylabs/account-service:latest platform: linux/amd64 ports: - 3003:3000 @@ -111,7 +111,7 @@ services: - amplica-gateway account-service-worker: - image: amplicalabs/account-service:latest + image: projectlibertylabs/account-service:latest platform: linux/amd64 command: worker env_file: diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index 21503830..ada5f64c 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -9,9 +9,9 @@ The application requires a Redis server that is configured with `Append-only fil ### Standalone (complete) image -The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/) and deploy using your favorite container management system. +The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/projectlibertylabs/content-watcher-service/) and deploy using your favorite container management system. ``` - docker pull amplicalabs/content-watcher-service:standalone-latest + docker pull projectlibertylabs/content-watcher-service:standalone-latest ``` The internal Redis server included in the complete image is already configured for persistence; it is simply necessary to configure your container pod to map the directory `/var/lib/redis` to a persistent storage volume. @@ -22,9 +22,9 @@ Follow the instructions below for [configuration](#configuration), with the exce ### App-only image -The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/), simply: +The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/projectlibertylabs/content-watcher-service/), simply: ``` - docker pull amplicalabs/content-watcher-service:apponly-latest + docker pull projectlibertylabs/content-watcher-service:apponly-latest ``` In this case, you need to ensure that the following settings are configured in your Redis instance: ``` diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml index dc272b92..3e29ca9a 100644 --- a/services/content-watcher/docker-compose.yaml +++ b/services/content-watcher/docker-compose.yaml @@ -54,7 +54,7 @@ services: - ipfs_data:/data/ipfs content-publishing-service-api: - image: amplicalabs/content-publishing-service:latest + image: projectlibertylabs/content-publishing-service:latest # For now, this is the only platform image published. platform: linux/amd64 ports: @@ -72,7 +72,7 @@ services: - content-watcher-service content-publishing-service-worker: - image: amplicalabs/content-publishing-service:latest + image: projectlibertylabs/content-publishing-service:latest # For now, this is the only platform image published. platform: linux/amd64 env_file: From d16dbf92c6ce4e57047319089d3c50b6176e4406 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Fri, 2 Aug 2024 16:04:18 -0400 Subject: [PATCH 2/6] 327 fix docker build for new relative packages (#328) - Fix Docker release image build for all services - Move all Dockerfiles to top-level `Docker/Dockerfile.` - Consolidate all "dev" images to a single `Dockerfile.dev` image that can run any app/service from a locally-mounted volume - Add `docker-build-` targets to top-level Makefile - Invoke `docker-build-` from CI release pipeline - Copy `start-gateway.sh`, `stop-gateway.sh`, and `docker-compose.yaml` from the social-app-template repo, with some modifications - Remove all "docker:build" scripts from each service `package.json` - Remove all obsolete service-level Dockerfile, docker-compose, and .env.docker files Closes #327 --------- Co-authored-by: Wil Wade --- .github/workflows/release.yml | 10 +- .gitignore | 2 +- .../Dockerfile => Docker/Dockerfile.account | 27 ++- Docker/Dockerfile.content-publishing | 45 ++++ Docker/Dockerfile.content-watcher | 40 ++++ Docker/Dockerfile.dev | 7 + Docker/Dockerfile.graph | 45 ++++ Makefile | 25 ++- README.md | 8 +- docker-compose.yaml | 204 +++++++++++++----- environment/env.account-service.template | 38 ---- environment/env.common.template | 49 ----- .../env.content-publishing-service.template | 42 ---- .../env.content-watcher-service.template | 33 --- environment/env.graph-service.template | 85 -------- services/account/dev.Dockerfile | 9 - services/account/docker-compose.yaml | 106 --------- services/account/package-lock.json | 1 + services/account/package.json | 5 - services/content-publishing/.env.docker.dev | 51 ----- services/content-publishing/Dockerfile | 38 ---- .../worker/src/publisher/nonce.service.ts | 2 +- services/content-publishing/dev.Dockerfile | 13 -- .../content-publishing/docker-compose.yaml | 92 -------- services/content-publishing/package.json | 5 - services/content-publishing/swagger.json | 94 -------- services/content-watcher/.env.docker.dev | 39 ---- services/content-watcher/Dockerfile | 33 --- services/content-watcher/dev.Dockerfile | 22 -- services/content-watcher/docker-compose.yaml | 120 ----------- services/content-watcher/package.json | 5 - services/graph/Dockerfile | 37 ---- services/graph/dev.Dockerfile | 15 -- services/graph/docker-compose.yaml | 94 -------- services/graph/package-lock.json | 1 + services/graph/package.json | 2 - start-gateway.sh | 189 ++++++++++++++++ stop-gateway.sh | 33 +++ 38 files changed, 563 insertions(+), 1103 deletions(-) rename services/account/Dockerfile => Docker/Dockerfile.account (50%) create mode 100644 Docker/Dockerfile.content-publishing create mode 100644 Docker/Dockerfile.content-watcher create mode 100644 Docker/Dockerfile.dev create mode 100644 Docker/Dockerfile.graph delete mode 100644 environment/env.account-service.template delete mode 100644 environment/env.common.template delete mode 100644 environment/env.content-publishing-service.template delete mode 100644 environment/env.content-watcher-service.template delete mode 100644 environment/env.graph-service.template delete mode 100644 services/account/dev.Dockerfile delete mode 100644 services/account/docker-compose.yaml delete mode 100644 services/content-publishing/.env.docker.dev delete mode 100644 services/content-publishing/Dockerfile delete mode 100644 services/content-publishing/dev.Dockerfile delete mode 100644 services/content-publishing/docker-compose.yaml delete mode 100644 services/content-watcher/.env.docker.dev delete mode 100644 services/content-watcher/Dockerfile delete mode 100644 services/content-watcher/dev.Dockerfile delete mode 100644 services/content-watcher/docker-compose.yaml delete mode 100644 services/graph/Dockerfile delete mode 100644 services/graph/dev.Dockerfile delete mode 100644 services/graph/docker-compose.yaml create mode 100755 start-gateway.sh create mode 100755 stop-gateway.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9702ef4..e044ce76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ -name: "Release" -run-name: "Cut Release ${{ github.event.release.tag_name || github.event.inputs.release-tag}}" +name: 'Release' +run-name: 'Cut Release ${{ github.event.release.tag_name || github.event.inputs.release-tag}}' concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true @@ -9,7 +9,7 @@ on: workflow_dispatch: inputs: release-tag: - description: "Release tag ([servicename-]v#.#.#[-rc#])" + description: 'Release tag ([servicename-]v#.#.#[-rc#])' required: true env: @@ -112,8 +112,8 @@ jobs: - name: Build and Push account-service-service Image uses: docker/build-push-action@v5 with: - context: services/${{ matrix.service }} + context: . platforms: linux/amd64 push: ${{ needs.set_variables.outputs.test_run != 'true'}} - file: services/account/Dockerfile + file: Docker/Dockerfile.${{ matrix.service }} tags: ${{ steps.cp-tags.outputs.tags }} diff --git a/.gitignore b/.gitignore index aad362cf..a23fb2f0 100644 --- a/.gitignore +++ b/.gitignore @@ -75,7 +75,7 @@ web_modules/ .yarn-integrity # dotenv environment variable files -.env +.env* .env.* # parcel-bundler cache (https://parceljs.org/) diff --git a/services/account/Dockerfile b/Docker/Dockerfile.account similarity index 50% rename from services/account/Dockerfile rename to Docker/Dockerfile.account index cb2ede76..9f0582c8 100644 --- a/services/account/Dockerfile +++ b/Docker/Dockerfile.account @@ -1,30 +1,37 @@ # Use a multi-stage build for efficiency FROM node:20 AS builder -WORKDIR /app +WORKDIR /build/packages + +COPY packages ./ + +WORKDIR /build/services/account -COPY package*.json ./ +COPY services/account/package* ./ RUN npm ci -COPY . . +COPY services/account ./ # Build the application -RUN npm run build +RUN npm run build && \ + rm -rf node_modules && \ + npm ci --omit=dev # Production stage FROM node:20 +ENV NODE_ENV=production + WORKDIR /app -COPY --from=builder /app/dist ./dist -COPY package*.json ./ -COPY ./lua ./lua -COPY ./scripts/docker-entrypoint.sh ./ +COPY --from=builder /build/services/account/dist ./dist/ +COPY services/account/package*.json ./ +COPY services/account/lua ./lua/ +COPY services/account/scripts/docker-entrypoint.sh ./ +COPY --from=builder /build/services/account/node_modules ./node_modules/ RUN chmod +x ./docker-entrypoint.sh -RUN npm ci --omit=dev - # We want jq and curl in the final image, but we don't need the support files RUN apt-get update && \ apt-get install -y jq curl tini && \ diff --git a/Docker/Dockerfile.content-publishing b/Docker/Dockerfile.content-publishing new file mode 100644 index 00000000..e7104a9e --- /dev/null +++ b/Docker/Dockerfile.content-publishing @@ -0,0 +1,45 @@ +# Use a multi-stage build for efficiency +FROM node:20 AS builder + +WORKDIR /build/packages + +COPY packages ./ + +WORKDIR /build/services/content-publishing + +COPY services/content-publishing/package* ./ + +RUN npm ci + +COPY services/content-publishing ./ + +# Build the application +RUN npm run build && \ + rm -rf node_modules && \ + npm ci --omit=dev + +# Production stage +FROM node:20 + +ENV NODE_ENV=production + +WORKDIR /app + +COPY --from=builder /build/services/content-publishing/dist ./dist/ +COPY services/content-publishing/package*.json ./ +COPY services/content-publishing/lua ./lua/ +COPY services/content-publishing/scripts/docker-entrypoint.sh ./ +COPY --from=builder /build/services/content-publishing/node_modules ./node_modules/ +RUN chmod +x ./docker-entrypoint.sh + +# We want jq and curl in the final image, but we don't need the support files +RUN apt-get update && \ + apt-get install -y jq curl tini && \ + apt-get clean && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/zsh + +EXPOSE 3000 + +ENV START_PROCESS="api" + +ENTRYPOINT ["/usr/bin/tini", "--", "./docker-entrypoint.sh"] diff --git a/Docker/Dockerfile.content-watcher b/Docker/Dockerfile.content-watcher new file mode 100644 index 00000000..8f060d31 --- /dev/null +++ b/Docker/Dockerfile.content-watcher @@ -0,0 +1,40 @@ +# Use a multi-stage build for efficiency +FROM node:20 AS builder + +WORKDIR /build/packages + +COPY packages ./ + +WORKDIR /build/services/content-watcher + +COPY services/content-watcher/package* ./ + +RUN npm ci + +COPY services/content-watcher ./ + +# Build the application +RUN npm run build && \ + rm -rf node_modules && \ + npm ci --omit=dev + +# Production stage +FROM node:20 + +ENV NODE_ENV=production + +WORKDIR /app + +COPY --from=builder /build/services/content-watcher/dist ./dist/ +COPY services/content-watcher/package*.json ./ +COPY --from=builder /build/services/content-watcher/node_modules ./node_modules/ + +# We want jq and curl in the final image, but we don't need the support files +RUN apt-get update && \ + apt-get install -y jq curl tini && \ + apt-get clean && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/zsh + +EXPOSE 3000 + +ENTRYPOINT [ "/usr/bin/tini", "--", "node", "dist/apps/api/main.js" ] diff --git a/Docker/Dockerfile.dev b/Docker/Dockerfile.dev new file mode 100644 index 00000000..0c2d6852 --- /dev/null +++ b/Docker/Dockerfile.dev @@ -0,0 +1,7 @@ +FROM node:20 + +WORKDIR /app + +EXPOSE 3000 + +VOLUME [ "/app", "/app/services/account/node_modules", "/app/services/graph/node_modules", "/app/services/content-publishing/node_modules", "/app/services/content-watcher/node_modules" ] diff --git a/Docker/Dockerfile.graph b/Docker/Dockerfile.graph new file mode 100644 index 00000000..d32d4c33 --- /dev/null +++ b/Docker/Dockerfile.graph @@ -0,0 +1,45 @@ +# Use a multi-stage build for efficiency +FROM node:20 AS builder + +WORKDIR /build/packages + +COPY packages ./ + +WORKDIR /build/services/graph + +COPY services/graph/package* ./ + +RUN npm ci + +COPY services/graph ./ + +# Build the application +RUN npm run build && \ + rm -rf node_modules && \ + npm ci --omit=dev + +# Production stage +FROM node:20 + +ENV NODE_ENV=production + +WORKDIR /app + +COPY --from=builder /build/services/graph/dist ./dist/ +COPY services/graph/package*.json ./ +COPY services/graph/lua ./lua/ +COPY services/graph/scripts/docker-entrypoint.sh ./ +COPY --from=builder /build/services/graph/node_modules ./node_modules/ +RUN chmod +x ./docker-entrypoint.sh + +# We want jq and curl in the final image, but we don't need the support files +RUN apt-get update && \ + apt-get install -y jq curl tini && \ + apt-get clean && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/zsh + +EXPOSE 3000 + +ENV START_PROCESS="api" + +ENTRYPOINT ["/usr/bin/tini", "--", "./docker-entrypoint.sh", "prod"] diff --git a/Makefile b/Makefile index d60bbbe5..939ee40f 100644 --- a/Makefile +++ b/Makefile @@ -64,4 +64,27 @@ $(FORMAT_TARGETS): @( cd services/$(@:format-%=%) ; npm run format ) $(DOCKER_BUILD_TARGETS): - @( cd services/$(@:docker-build-%=%) ; npm run docker:build ) + @docker build -t $(@:docker-build-%=%)-service -f Docker/Dockerfile.$(@:docker-build-%=%) . + +docker-build: $(DOCKER_BUILD_TARGETS) + +start-account-api: + @( cd services/account ; npm i ; npm run start:api:watch ) + +start-account-worker: + @( cd services/account ; npm i ; npm run start:worker:watch ) + +start-content-publishing-api: + @( cd services/content-publishing ; npm i ; npm run start:api:watch ) + +start-content-publishing-worker: + @( cd services/content-publishing ; npm i ; npm run start:worker:watch ) + +start-content-watcher: + @( cd services/content-watcher ; npm i ; npm run start:watch ) + +start-graph-api: + @( cd services/graph ; npm i ; npm run start:api:watch ) + +start-graph-worker: + @( cd services/graph ; npm i ; npm run start:worker:watch ) diff --git a/README.md b/README.md index e7373684..da5bb115 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,13 @@ Clone: To run all Gateway services, execute the following command: ```sh - docker compose docker-compose.all.yaml + ./start-gateway.sh +``` + +To stop the Gateway services, execute the following command: + +```sh + ./stop-gateway.sh ``` To build the Gateway Documentation: diff --git a/docker-compose.yaml b/docker-compose.yaml index 3b309122..35205309 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,3 +1,49 @@ +x-common-environment: &common-environment + FREQUENCY_URL: ${FREQUENCY_URL:-ws://frequency:9944} + FREQUENCY_HTTP_URL: ${FREQUENCY_HTTP_URL:-http://localhost:9944} + REDIS_URL: 'redis://redis:6379' + PROVIDER_ID: ${PROVIDER_ID:-1} + PROVIDER_ACCOUNT_SEED_PHRASE: ${PROVIDER_ACCOUNT_SEED_PHRASE:-//Alice} + WEBHOOK_BASE_URL: 'http://social-app-template-backend:3001/webhooks' + WEBHOOK_FAILURE_THRESHOLD: 3 + WEBHOOK_RETRY_INTERVAL_SECONDS: 10 + HEALTH_CHECK_MAX_RETRIES: 4 + HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 10 + HEALTH_CHECK_SUCCESS_THRESHOLD: 10 + CAPACITY_LIMIT: '{"type":"percentage", "value":80}' + SIWF_URL: 'https://amplicalabs.github.io/siwf/ui' + SIWF_DOMAIN: 'localhost' + IPFS_ENDPOINT: ${IPFS_ENDPOINT:-http://ipfs:5001} + IPFS_GATEWAY_URL: ${IPFS_GATEWAY_URL:-https://ipfs.io/ipfs/[CID]} + IPFS_BASIC_AUTH_USER: ${IPFS_BASIC_AUTH_USER:-""} + IPFS_BASIC_AUTH_SECRET: ${IPFS_BASIC_AUTH_SECRET:-""} + QUEUE_HIGH_WATER: 1000 + CHAIN_ENVIRONMENT: dev + DEBUG: true + +x-content-publishing-env: &content-publishing-env + START_PROCESS: api + FILE_UPLOAD_MAX_SIZE_IN_BYTES: 2000000000 + ASSET_EXPIRATION_INTERVAL_SECONDS: 300 + BATCH_INTERVAL_SECONDS: 12 + BATCH_MAX_COUNT: 1000 + ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: 5 + +x-content-watcher-env: &content-watcher-env + STARTING_BLOCK: 759882 + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 6 + WEBHOOK_FAILURE_THRESHOLD: 4 + +x-graph-service-env: &graph-service-env + DEBOUNCE_SECONDS: 10 + GRAPH_ENVIRONMENT_TYPE: Mainnet + RECONNECTION_SERVICE_REQUIRED: false + +x-account-service-env: &account-service-env + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 1 + TRUST_UNFINALIZED_BLOCKS: true + PROVIDER_BASE_URL: 'http://social-app-template-backend:3001/webhooks/account-service' + services: redis: image: redis:latest @@ -15,9 +61,9 @@ services: platform: linux/amd64 # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. # Other options you may want to add depending on your test scenario. - # environment: - # - SEALING_MODE=interval - # - SEALING_INTERVAL=3 + environment: + - SEALING_MODE=interval + - SEALING_INTERVAL=12 # - CREATE_EMPTY_BLOCKS=true # The 'command' may contain additional CLI options to the Frequency node, # such as: @@ -29,99 +75,139 @@ services: - amplica-gateway volumes: - chainstorage:/data + profiles: + - local-node - kubo_ipfs: + ipfs: image: ipfs/kubo:latest ports: - 4001:4001 - - 5001:5001 - - 8080:8080 + - 127.0.0.1:5001:5001 + - 127.0.0.1:8080:8080 networks: - amplica-gateway volumes: - - ipfs_data:/data/ipfs + - ipfs_data:${IPFS_VOLUME:-/data/ipfs} + + gateway-base: + pull_policy: never + image: gateway-dev:latest + build: + context: . + dockerfile: Docker/Dockerfile.dev + tags: + - gateway-dev:latest content-publishing-service-api: - image: projectlibertylabs/content-publishing-service:latest - platform: linux/amd64 + image: gateway-dev:latest ports: - - 3001:3000 - env_file: - - path: .env.common - required: true - - path: .env.content-publishing-service - required: false + - ${SERVICE_PORT_0:-3010}:3000 + command: make start-content-publishing-api environment: - - START_PROCESS=api + <<: [*common-environment, *content-publishing-env] + volumes: + - ./:/app + - content_publishing_node_cache:/app/services/content-publishing/node_modules depends_on: - redis - - frequency - - kubo_ipfs + - ipfs + - gateway-base networks: - amplica-gateway content-publishing-service-worker: - image: projectlibertylabs/content-publishing-service:latest - platform: linux/amd64 - env_file: - - path: .env.common - required: true - - path: .env.content-publishing-service - required: false + image: gateway-dev:latest + command: make start-content-publishing-worker environment: - - START_PROCESS=worker + <<: [*common-environment, *content-publishing-env] + volumes: + - ./:/app + - content_publishing_node_cache:/app/services/content-publishing/node_modules depends_on: - redis - - frequency - - kubo_ipfs + - ipfs + - gateway-base networks: - amplica-gateway - graph-service: - image: projectlibertylabs/graph-service:latest - platform: linux/amd64 + content-watcher-service: + image: gateway-dev:latest ports: - - 3002:3000 - env_file: - - path: .env.common - required: true - - path: .env.graph-service - required: false + - ${SERVICE_PORT_1:-3011}:3000 + command: make start-content-watcher + environment: + <<: [*common-environment, *content-watcher-env] + volumes: + - ./:/app + - content_watcher_node_cache:/app/services/content-watcher/node_modules depends_on: - redis - - frequency + - ipfs + - gateway-base + networks: + - amplica-gateway + + graph-service-api: + image: gateway-dev:latest + ports: + - ${SERVICE_PORT_2:-3012}:3000 + command: make start-graph-api + environment: + <<: [*common-environment, *graph-service-env] + volumes: + - ./:/app + - graph_node_cache:/app/services/graph/node_modules + depends_on: + - redis + - ipfs + - gateway-base + networks: + - amplica-gateway + + graph-service-worker: + image: gateway-dev:latest + command: make start-graph-worker + environment: + <<: [*common-environment, *graph-service-env] + volumes: + - ./:/app + - graph_node_cache:/app/services/graph/node_modules + depends_on: + - redis + - ipfs + - gateway-base networks: - amplica-gateway account-service-api: - image: projectlibertylabs/account-service:latest - platform: linux/amd64 + image: gateway-dev:latest ports: - - 3003:3000 - command: api - env_file: - - path: .env.common - required: true - - path: .env.account-service - required: false + - ${SERVICE_PORT_3:-3013}:3000 + environment: + <<: [*common-environment, *account-service-env] + command: make start-account-api + volumes: + - ./:/app + - account_node_cache:/app/services/account/node_modules depends_on: - redis - - frequency + - ipfs + - gateway-base networks: - amplica-gateway account-service-worker: - image: projectlibertylabs/account-service:latest - platform: linux/amd64 - command: worker - env_file: - - path: .env.common - required: true - - path: .env.account-service - required: false + image: gateway-dev:latest + command: make start-account-worker + environment: + <<: [*common-environment, *account-service-env] + volumes: + - ./:/app + - account_node_cache:/app/services/account/node_modules depends_on: - redis - - frequency + - ipfs + - gateway-base networks: - amplica-gateway @@ -131,6 +217,10 @@ volumes: chainstorage: external: false redis_data: + account_node_cache: + graph_node_cache: + content_publishing_node_cache: + content_watcher_node_cache: networks: amplica-gateway: diff --git a/environment/env.account-service.template b/environment/env.account-service.template deleted file mode 100644 index 2c662ff4..00000000 --- a/environment/env.account-service.template +++ /dev/null @@ -1,38 +0,0 @@ -# Copy this file to "/.env.account-service", and then tweak values for local development -# Values in this file will override the same-named environnent variables in `.env.common.docker` for the account-service - -# Base URL for provider webhook endpoints -# PROVIDER_BASE_URL=https://some-provider/api/v1.0.0 - -# An optional bearer token authentication to the provider webhook -# PROVIDER_ACCESS_TOKEN=some-token - -# Number of failures allowing in the provider webhook before the service is marked down -# WEBHOOK_FAILURE_THRESHOLD=3 - -# Number of seconds between provider webhook retry attempts when failing -# WEBHOOK_RETRY_INTERVAL_SECONDS=10 - -# Number of `/health` endpoint failures allowed before marking the provider webhook service down -# HEALTH_CHECK_MAX_RETRIES=4 - -# Number of seconds to retry provider webhook `/health` endpoint when failing -# HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 - -# Minimum number of consecutive successful calls to the provider webhook -# `/health` endpoint before it is marked up again -# HEALTH_CHECK_SUCCESS_THRESHOLD=10 - -# Maximum amount of provider capacity this app is allowed to use (per epoch) -# type: 'percentage' | 'amount' -# value: number (may be percentage, ie '80', or absolute amount of capacity) -# CAPACITY_LIMIT='{"type":"percentage", "value":80}' - -# URL for the Sign-In With Frequency UI -# SIWF_URL=https://amplicalabs.github.io/siwf/ui - -# Domain for the Sign-in with Frequency login payload -# SIWF_DOMAIN=localhost - -# Enable debug mode for development -# DEBUG=true diff --git a/environment/env.common.template b/environment/env.common.template deleted file mode 100644 index 138687c9..00000000 --- a/environment/env.common.template +++ /dev/null @@ -1,49 +0,0 @@ -# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development - -# Blockchain node address -FREQUENCY_URL=ws://0.0.0.0:9944 - -# Specifies the provider ID -PROVIDER_ID=1 - -# Base URL for provider webhook endpoints -PROVIDER_BASE_URL=https://some-provider/api/v1.0.0 - -# Redis URL -REDIS_URL=redis://0.0.0.0:6379 - -# An optional bearer token authentication to the provider webhook -PROVIDER_ACCESS_TOKEN=some-token - -# Seed phrase for provider MSA control key -PROVIDER_ACCOUNT_SEED_PHRASE='come finish flower cinnamon blame year glad tank domain hunt release fatigue' - -# Number of failures allowing in the provider webhook before the service is marked down -WEBHOOK_FAILURE_THRESHOLD=3 - -# Number of seconds between provider webhook retry attempts when failing -WEBHOOK_RETRY_INTERVAL_SECONDS=10 - -# Number of `/health` endpoint failures allowed before marking the provider webhook service down -HEALTH_CHECK_MAX_RETRIES=4 - -# Number of seconds to retry provider webhook `/health` endpoint when failing -HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 - -# Minimum number of consecutive successful calls to the provider webhook -# `/health` endpoint before it is marked up again -HEALTH_CHECK_SUCCESS_THRESHOLD=10 - -# Maximum amount of provider capacity this app is allowed to use (per epoch) -# type: 'percentage' | 'amount' -# value: number (may be percentage, ie '80', or absolute amount of capacity) -CAPACITY_LIMIT='{"type":"percentage", "value":80}' - -# URL for the Sign-In With Frequency UI -SIWF_URL=https://amplicalabs.github.io/siwf/ui - -# Domain for the Sign-in with Frequency login payload -SIWF_DOMAIN=localhost - -# Enable debug mode for development -DEBUG=false diff --git a/environment/env.content-publishing-service.template b/environment/env.content-publishing-service.template deleted file mode 100644 index 6ab4d47b..00000000 --- a/environment/env.content-publishing-service.template +++ /dev/null @@ -1,42 +0,0 @@ -# Copy this file to "/.env.content-publishing-service", and then tweak values for local development -# Values in this file will override the same-named environnent variables in `.env.common.docker` for the content-publishing-service - -# URL to IPFS endpoint -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -IPFS_ENDPOINT="http://kubo_ipfs:5001" - -# If using Infura, put Project ID here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_USER= - -# If using Infura, put auth token here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_SECRET= - -# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" - -CAPACITY_LIMIT='{"type":"percentage", "value":80}' - -# Environment for mapping announcement type to schema ID (use 'dev' for e2e tests) -# Possible values: dev, rococo, testnet, mainnet -CHAIN_ENVIRONMENT=dev - -# Max file size allowed for asset upload -FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 - -# Number of seconds to keep completed asset entrie in the cache -# before expiring them -ASSET_EXPIRATION_INTERVAL_SECONDS=300 - -# Number of seconds between content publishing batches. This is so that -# the service waits a reasonable amount of time for additional content to publishing -# before submitting a batch--it represents a trade-off between maximum batch fullness -# and minimal wait time for published content. -BATCH_INTERVAL_SECONDS=12 - -# Maximum number of items that can be submitted in a single batch -BATCH_MAX_COUNT=1000 - -# Base delay in seconds used for exponential backoff while waiting for -# uploaded assets to be verified available before publishing a content notice. -ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 diff --git a/environment/env.content-watcher-service.template b/environment/env.content-watcher-service.template deleted file mode 100644 index 8ecb729d..00000000 --- a/environment/env.content-watcher-service.template +++ /dev/null @@ -1,33 +0,0 @@ -# Copy this file to "/.env.content-watcher-service", and then tweak values for local development -# Values in this file will override the same-named environnent variables in `.env.common.docker` for the content-watcher-service - -# URL to IPFS endpoint -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -IPFS_ENDPOINT="http://kubo_ipfs:5001" - -# If using Infura, put Project ID here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_USER= - -# If using Infura, put auth token here, or leave blank for Kubo RPC -IPFS_BASIC_AUTH_SECRET= - -# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" - -# Block number from which the service will start scanning the chain -STARTING_BLOCK=1 - -# How many minutes to delay between successive scans of the chain -# for new accounts (after end of chain is reached) -BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 - -# Max number of jobs allowed on the queue before -# blockchain scan will be paused to allow queue to drain -QUEUE_HIGH_WATER=1000 - -# Number of retry attempts if a registered webhook call fails -WEBHOOK_FAILURE_THRESHOLD=4 - -# Number of seconds between webhook retry attempts when failing -WEBHOOK_RETRY_INTERVAL_SECONDS=10 diff --git a/environment/env.graph-service.template b/environment/env.graph-service.template deleted file mode 100644 index 60879ccb..00000000 --- a/environment/env.graph-service.template +++ /dev/null @@ -1,85 +0,0 @@ -# Copy this file to "/.env.graph-service", and then tweak values for local development -# Values in this file will override the same-named environnent variables in `.env.common.docker` for the graph-service - -# Max number of jobs allowed on the 'graphUpdateQueue' before -# blockchain scan will be paused to allow queue to drain -QUEUE_HIGH_WATER=1000 - -# Number of seconds to retain pending graph updates in the Redis cache to avoid redundant fetches from the chain -DEBOUNCE_SECONDS=10 - -# Maximum amount of provider capacity this app is allowed to use (per epoch) -# type: 'percentage' | 'amount' -# value: number (may be percentage, ie '80', or absolute amount of capacity) -CAPACITY_LIMIT='{"type":"percentage", "value":80}' - -# Graph environment type. This can be 'Dev' or 'Rococo', 'TestnetPaseo', or 'Mainnet'. -GRAPH_ENVIRONMENT_TYPE=Dev - -# [NOTE]: The following config is only used for Dev environments. -# Add the graph environment config in JSON format only used for Dev environments. -# Be careful to escape any inner quotes as this is in a .env file. -GRAPH_ENVIRONMENT_DEV_CONFIG='{ - "sdkMaxStaleFriendshipDays": 100, - "maxPageId": 100, - "dsnpVersions": [ - "1.0" - ], - "maxGraphPageSizeBytes": 100, - "maxKeyPageSizeBytes": 100, - "schemaMap": { - "1": { - "dsnpVersion": "1.0", - "connectionType": "follow", - "privacyType": "public" - }, - "2": { - "dsnpVersion": "1.0", - "connectionType": "follow", - "privacyType": "private" - }, - "3": { - "dsnpVersion": "1.0", - "connectionType": "friendship", - "privacyType": "private" - } - }, - "graphPublicKeySchemaId": 4 - } - ' -PROVIDER_ACCOUNT_SEED_PHRASE="//Ferdie" - -# Whether to instantiate/activate reconnection-service features -RECONNECTION_SERVICE_REQUIRED=false - -### The following are only applicable if RECONNECTION_SERVICE_REQUIRED is 'true' - -# Base URL for provider webhook endpoints -# PROVIDER_BASE_URL=https://some-provider/api/v1.0.0 - -# An optional bearer token authentication to the provider webhook -# PROVIDER_ACCESS_TOKEN=some-token - -# Number of connection/page to request when requesting provider connections from webhook -C# ONNECTIONS_PER_PROVIDER_RESPONSE_PAGE=100 - - -# How many minutes to delay between successive scans of the chain -# for new accounts (after end of chain is reached) -# BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 - -# Number of failures allowing in the provider webhook before the service is marked down -# WEBHOOK_FAILURE_THRESHOLD=3 - -# Minimum number of consecutive successful calls to the provider webhook -# `/health` endpoint before it is marked up again -# HEALTH_CHECK_SUCCESS_THRESHOLD=10 - -# Number of seconds between provider webhook retry attempts when failing -# WEBHOOK_RETRY_INTERVAL_SECONDS=10 - -# Number of seconds to retry provider webhook `/health` endpoint when failing -# HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 - -# Number of `/health` endpoint failures allowed before marking the provider webhook service down -# HEALTH_CHECK_MAX_RETRIES=4 diff --git a/services/account/dev.Dockerfile b/services/account/dev.Dockerfile deleted file mode 100644 index 80d919d3..00000000 --- a/services/account/dev.Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM node:20 - -WORKDIR /app - -EXPOSE 3000 - -VOLUME [ "/app" ] - -ENTRYPOINT [ "./scripts/docker-entrypoint.sh", "watch" ] diff --git a/services/account/docker-compose.yaml b/services/account/docker-compose.yaml deleted file mode 100644 index e906a0f2..00000000 --- a/services/account/docker-compose.yaml +++ /dev/null @@ -1,106 +0,0 @@ -x-account-service-env: &account-service-env - API_PORT: 3000 - BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 3 - TRUST_UNFINALIZED_BLOCKS: false - FREQUENCY_URL: 'ws://frequency:9944' - FREQUENCY_HTTP_URL: 'http://frequency:9944' - PROVIDER_ID: 1 - PROVIDER_BASE_URL: 'http://host.docker.internal:3001/webhooks/account-service' - REDIS_URL: 'redis://redis:6379' - PROVIDER_ACCESS_TOKEN: 'some-token' - PROVIDER_ACCOUNT_SEED_PHRASE: '//Alice' - WEBHOOK_FAILURE_THRESHOLD: 3 - WEBHOOK_RETRY_INTERVAL_SECONDS: 10 - HEALTH_CHECK_MAX_RETRIES: 4 - HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 10 - HEALTH_CHECK_SUCCESS_THRESHOLD: 10 - CAPACITY_LIMIT: '{"type":"percentage", "value":80}' - SIWF_URL: 'https://amplicalabs.github.io/siwf/ui' - SIWF_DOMAIN: localhost - DEBUG: false - -services: - redis: - image: redis:latest - ports: - - 6379:6379 - volumes: - - redis_data:/data/redis - networks: - - account-service - - frequency: - image: dsnp/instant-seal-node-with-deployed-schemas:latest - # We need to specify the platform because it's the only image - # built by Frequency at the moment, and auto-pull won't work otherwise - platform: linux/amd64 - # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. - # Other options you may want to add depending on your test scenario. - environment: - - SEALING_MODE=interval - - SEALING_INTERVAL=1 - # - CREATE_EMPTY_BLOCKS=true - # Uncomment below if you want to let the chain run and keep all of the historical blocks - # command: --state-pruning=archive - command: --offchain-worker=always --enable-offchain-indexing=true - ports: - - 9944:9944 - networks: - - account-service - volumes: - - chainstorage:/data - - account-service-base: - pull_policy: never - image: account-service:latest - environment: - <<: *account-service-env - build: - context: . - dockerfile: dev.Dockerfile - tags: - - account-service:latest - volumes: - - ./:/app - - api: - pull_policy: never - image: account-service:latest - environment: - <<: *account-service-env - command: ['api'] - ports: - - 3000:3000 - volumes: - - ./:/app - depends_on: - - account-service-base - - redis - - frequency - networks: - - account-service - restart: on-failure - - worker: - pull_policy: never - image: account-service:latest - environment: - <<: *account-service-env - command: ['worker'] - volumes: - - ./:/app - depends_on: - - account-service-base - - redis - - frequency - networks: - - account-service - restart: on-failure - -volumes: - redis_data: - chainstorage: - external: false - -networks: - account-service: diff --git a/services/account/package-lock.json b/services/account/package-lock.json index 41237915..34556cf0 100644 --- a/services/account/package-lock.json +++ b/services/account/package-lock.json @@ -90,6 +90,7 @@ } }, "../../packages/ts-config": { + "name": "@amplica-labs/ts-config", "version": "1.0.0", "dev": true, "license": "Apache-2.0" diff --git a/services/account/package.json b/services/account/package.json index a30964a0..8259247a 100644 --- a/services/account/package.json +++ b/services/account/package.json @@ -21,11 +21,6 @@ "start:worker:prod": "node dist/apps/worker/main.js", "start:worker:dev": "set -a ; . .env ; nest start worker", "start:worker:debug": "set -a ; . .env ; nest start worker --debug=9230 --watch", - "docker:build": "docker build -t account-service .", - "docker:build:dev": "docker-compose build", - "docker:run": " build -t account-service-deploy . ; docker run -p 6379:6379 --env-file .env account-service-deploy", - "docker:run:dev": "docker-compose up -d ; docker-compose -f docker-compose.dev.yaml logs", - "docker:stop:dev": "docker-compose stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", diff --git a/services/content-publishing/.env.docker.dev b/services/content-publishing/.env.docker.dev deleted file mode 100644 index 02bc678b..00000000 --- a/services/content-publishing/.env.docker.dev +++ /dev/null @@ -1,51 +0,0 @@ -# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development - -# URL to IPFS endpoint -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -IPFS_ENDPOINT="http://127.0.0.1:5001" - -# If using Infura, put Project ID here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_USER= - -# If using Infura, put auth token here, or leave blank for Kubo RPC -IPFS_BASIC_AUTH_SECRET= - -# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" - -# Blockchain node address -FREQUENCY_URL=ws://0.0.0.0:9944 - -PROVIDER_ID=1 -# Redis URL -REDIS_URL=redis://0.0.0.0:6379 -PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" -CAPACITY_LIMIT='{"type":"percentage", "value":80}' - -# Port that the application REST endpoints listen on -API_PORT=3000 - -# Environment for mapping announcement type to schema ID (use 'dev' for e2e tests) -# Possible values: dev, rococo, testnet, mainnet -CHAIN_ENVIRONMENT=dev - -# Max file size allowed for asset upload -FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 - -# Number of seconds to keep completed asset entrie in the cache -# before expiring them -ASSET_EXPIRATION_INTERVAL_SECONDS=300 - -# Number of seconds between content publishing batches. This is so that -# the service waits a reasonable amount of time for additional content to publishing -# before submitting a batch--it represents a trade-off between maximum batch fullness -# and minimal wait time for published content. -BATCH_INTERVAL_SECONDS=12 - -# Maximum number of items that can be submitted in a single batch -BATCH_MAX_COUNT=1000 - -# Base delay in seconds used for exponential backoff while waiting for -# uploaded assets to be verified available before publishing a content notice. -ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 diff --git a/services/content-publishing/Dockerfile b/services/content-publishing/Dockerfile deleted file mode 100644 index d7901660..00000000 --- a/services/content-publishing/Dockerfile +++ /dev/null @@ -1,38 +0,0 @@ -# Use a multi-stage build for efficiency -FROM node:20 AS builder - -WORKDIR /app - -COPY package*.json ./ - -RUN npm install - -COPY . . - -# Build the application -RUN npm run build - -# Production stage -FROM node:20 - -WORKDIR /app - -COPY --from=builder /app/dist ./dist -COPY package*.json ./ -COPY ./lua ./lua -COPY ./scripts/docker-entrypoint.sh ./ -RUN chmod +x ./docker-entrypoint.sh - -RUN npm ci --omit=dev - -# We want jq and curl in the final image, but we don't need the support files -RUN apt-get update && \ - apt-get install -y jq curl tini && \ - apt-get clean && \ - rm -rf /usr/share/doc /usr/share/man /usr/share/zsh - -EXPOSE 3000 - -ENV START_PROCESS="api" - -ENTRYPOINT ["/usr/bin/tini", "--", "./docker-entrypoint.sh"] diff --git a/services/content-publishing/apps/worker/src/publisher/nonce.service.ts b/services/content-publishing/apps/worker/src/publisher/nonce.service.ts index f23f35c7..a9255451 100644 --- a/services/content-publishing/apps/worker/src/publisher/nonce.service.ts +++ b/services/content-publishing/apps/worker/src/publisher/nonce.service.ts @@ -5,7 +5,7 @@ import fs from 'fs'; import { ConfigService } from '#libs/config'; import { BlockchainService } from '#libs/blockchain/blockchain.service'; import { createKeys } from '#libs/blockchain/create-keys'; -import { NUMBER_OF_NONCE_KEYS_TO_CHECK, getNonceKey } from '#libs/utils/redis'; +import { NUMBER_OF_NONCE_KEYS_TO_CHECK, NONCE_KEY_EXPIRE_SECONDS, getNonceKey } from '#libs/utils/redis'; @Injectable() export class NonceService implements OnApplicationBootstrap { diff --git a/services/content-publishing/dev.Dockerfile b/services/content-publishing/dev.Dockerfile deleted file mode 100644 index d5204f11..00000000 --- a/services/content-publishing/dev.Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20 - -WORKDIR /app - -# COPY . . - -# RUN npm install - -EXPOSE 3000 - -ENV START_PROCESS="api" - -ENTRYPOINT npm run start:${START_PROCESS}:watch diff --git a/services/content-publishing/docker-compose.yaml b/services/content-publishing/docker-compose.yaml deleted file mode 100644 index 68f39cdb..00000000 --- a/services/content-publishing/docker-compose.yaml +++ /dev/null @@ -1,92 +0,0 @@ -services: - redis: - image: redis:latest - ports: - - 6379:6379 - volumes: - - redis_data:/data/redis - networks: - - content-publishing-service - - frequency: - image: dsnp/instant-seal-node-with-deployed-schemas:latest - # We need to specify the platform because it's the only image - # built by Frequency at the moment, and auto-pull won't work otherwise - platform: linux/amd64 - # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. - # Other options you may want to add depending on your test scenario. - # environment: - # - SEALING_MODE=interval - # - SEALING_INTERVAL=3 - # - CREATE_EMPTY_BLOCKS=true - # Uncomment below if you want to let the chain run and keep all of the historical blocks - # command: --state-pruning=archive - ports: - - 9944:9944 - networks: - - content-publishing-service - volumes: - - frequency_data:/data/frequency - - ipfs: - image: ipfs/kubo:latest - ports: - - 4001:4001 - - 5001:5001 - - 8080:8080 - networks: - - content-publishing-service - volumes: - - ipfs_data:/data/ipfs - - content-publishing-service-api: - pull_policy: never - image: content-publishing-service - build: - context: . - dockerfile: dev.Dockerfile - tags: - - content-publishing-service:latest - ports: - - 3000:3000 - env_file: - - .env.docker.dev - environment: - - START_PROCESS=api - volumes: - - ./:/app - depends_on: - - redis - - frequency - - ipfs - networks: - - content-publishing-service - - content-publishing-service-worker: - pull_policy: never - image: content-publishing-service - build: - context: . - dockerfile: dev.Dockerfile - tags: - - content-publishing-service:latest - env_file: - - .env.docker.dev - environment: - - START_PROCESS=worker - volumes: - - ./:/app - depends_on: - - redis - - frequency - - ipfs - networks: - - content-publishing-service - -volumes: - redis_data: - ipfs_data: - frequency_data: - -networks: - content-publishing-service: diff --git a/services/content-publishing/package.json b/services/content-publishing/package.json index 34a05d60..48aec678 100644 --- a/services/content-publishing/package.json +++ b/services/content-publishing/package.json @@ -22,11 +22,6 @@ "start:worker:dev": "set -a ; . .env ; nest start worker --watch", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "start:worker:debug": "set -a ; . .env ; nest start worker --debug --watch", - "docker:build": "docker build -t content-publishing-service .", - "docker:build:dev": "docker-compose build", - "docker:run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env content-publishing-service-deploy", - "docker:run:dev": "docker-compose up -d ; docker-compose logs -f content-publishing-service", - "docker:stop:dev": "docker-compose stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", diff --git a/services/content-publishing/swagger.json b/services/content-publishing/swagger.json index 1fea1330..f6aa53fc 100644 --- a/services/content-publishing/swagger.json +++ b/services/content-publishing/swagger.json @@ -322,96 +322,6 @@ "health" ] } - }, - "/dev/request/{jobId}": { - "get": { - "operationId": "DevelopmentControllerV1_requestJob", - "summary": "Get a Job given a jobId", - "description": "ONLY enabled when ENVIRONMENT=\"dev\".", - "parameters": [ - { - "name": "jobId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "dev" - ] - } - }, - "/dev/asset/{assetId}": { - "get": { - "operationId": "DevelopmentControllerV1_getAsset", - "summary": "Get an Asset given an assetId", - "description": "ONLY enabled when ENVIRONMENT=\"dev\".", - "parameters": [ - { - "name": "assetId", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "2XX": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Buffer" - } - } - } - } - }, - "tags": [ - "dev" - ] - } - }, - "/dev/dummy/announcement/{queueType}/{count}": { - "post": { - "operationId": "DevelopmentControllerV1_populate", - "summary": "Create dummy announcement data", - "description": "ONLY enabled when ENVIRONMENT=\"dev\".", - "parameters": [ - { - "name": "queueType", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "count", - "required": true, - "in": "path", - "schema": { - "type": "number" - } - } - ], - "responses": { - "201": { - "description": "" - } - }, - "tags": [ - "dev" - ] - } } }, "info": { @@ -759,10 +669,6 @@ "required": [ "profile" ] - }, - "Buffer": { - "type": "object", - "properties": {} } } } diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev deleted file mode 100644 index 32474bf2..00000000 --- a/services/content-watcher/.env.docker.dev +++ /dev/null @@ -1,39 +0,0 @@ -# Tweak values for local development -# Content Publishing Service in Docker Compose will override these values with `.env.content-publishing-service` - -# URL to IPFS endpoint -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -IPFS_ENDPOINT="http://ipfs:5001" - -# If using Infura with auth required for read access, put Project ID here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_USER= - -# If using Infura with auth required for read access, put auth token here, or leave blank for Kubo RPC -# IPFS_BASIC_AUTH_SECRET= - -# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_GATEWAY_URL="http://ipfs:8080/ipfs/[CID]" - -# Blockchain node address -FREQUENCY_URL=ws://frequency:9944 - -# Redis URL -REDIS_URL=redis://redis:6379 - -# How many seconds to delay between successive scans of the chain -# for new content (after end of chain is reached) -BLOCKCHAIN_SCAN_INTERVAL_SECONDS=12 - -# Max number of jobs allowed on the queue before -# blockchain scan will be paused to allow queue to drain -QUEUE_HIGH_WATER=1000 - -# Number of retry attempts if a registered webhook call fails -WEBHOOK_FAILURE_THRESHOLD=4 - -# Number of seconds between webhook retry attempts when failing -WEBHOOK_RETRY_INTERVAL_SECONDS=10 - -# Port that the application REST endpoints listen on -API_PORT=3000 diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile deleted file mode 100644 index 2922244d..00000000 --- a/services/content-watcher/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# Use a multi-stage build for efficiency -FROM node:20 AS builder - -WORKDIR /app - -COPY package*.json ./ - -RUN npm ci - -COPY . . - -# Build the application -RUN npm run build - -# Production stage -FROM node:20 - -WORKDIR /app - -COPY --from=builder /app/dist ./dist -COPY package*.json ./ - -RUN npm ci --omit=dev - -# We want jq and curl in the final image, but we don't need the support files -RUN apt-get update && \ - apt-get install -y jq curl tini && \ - apt-get clean && \ - rm -rf /usr/share/doc /usr/share/man /usr/share/zsh - -EXPOSE 3000 - -ENTRYPOINT ["/usr/bin/tini", "--", "node", "dist/apps/api/main.js"] diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile deleted file mode 100644 index cb942c5b..00000000 --- a/services/content-watcher/dev.Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -# syntax=docker/dockerfile:1 - -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/go/dockerfile-reference/ - -# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 - -ARG NODE_VERSION=20 - -FROM node:${NODE_VERSION}-alpine - -WORKDIR /app - -# Run the application as a non-root user. -USER node - -# Expose the port that the application listens on. -EXPOSE 3000 - -# Run the application. -ENTRYPOINT [ "npm", "run", "start:watch" ] diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml deleted file mode 100644 index 3e29ca9a..00000000 --- a/services/content-watcher/docker-compose.yaml +++ /dev/null @@ -1,120 +0,0 @@ -x-content-watcher-environment: &content-watcher-environment - IPFS_GATEWAY_URL: 'https://ipfs:8080/ipfs/[CID]' - FREQUENCY_URL: 'ws://frequency:9944' - STARTING_BLOCK: 1 - REDIS_URL: 'redis://redis:6379' - BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 12 - QUEUE_HIGH_WATER: 1000 - WEBHOOK_FAILURE_THRESHOLD: 4 - WEBHOOK_RETRY_INTERVAL_SECONDS: 10 - API_PORT: 3000 - CAPACITY_LIMIT: '{"type":"percentage", "value":80}' - IPFS_ENDPOINT: 'http://ipfs:5001' - -services: - redis: - image: redis:latest - ports: - - 6379:6379 - volumes: - - redis_data:/data/redis - networks: - - content-watcher-service - - frequency: - image: dsnp/instant-seal-node-with-deployed-schemas:latest - # We need to specify the platform because it's the only image - # built by Frequency at the moment, and auto-pull won't work otherwise - platform: linux/amd64 - # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. - # Other options you may want to add depending on your test scenario. - environment: - - SEALING_MODE=interval - - SEALING_INTERVAL=1 - # - CREATE_EMPTY_BLOCKS=true - # Uncomment below if you want to let the chain run and keep all of the historical blocks - # command: --state-pruning=archive - ports: - - 9944:9944 - networks: - - content-watcher-service - container_name: frequency - volumes: - - chainstorage:/data - - ipfs: - image: ipfs/kubo:latest - ports: - - 4001:4001 - - 5001:5001 - - 8080:8080 - networks: - - content-watcher-service - volumes: - - ipfs_data:/data/ipfs - - content-publishing-service-api: - image: projectlibertylabs/content-publishing-service:latest - # For now, this is the only platform image published. - platform: linux/amd64 - ports: - - 3001:3000 - env_file: - - .env.docker.dev - - .env.content-publishing-service - environment: - - START_PROCESS=api - depends_on: - - redis - - frequency - - ipfs - networks: - - content-watcher-service - - content-publishing-service-worker: - image: projectlibertylabs/content-publishing-service:latest - # For now, this is the only platform image published. - platform: linux/amd64 - env_file: - - .env.docker.dev - - .env.content-publishing-service - environment: - - START_PROCESS=worker - depends_on: - - redis - - frequency - - ipfs - networks: - - content-watcher-service - - content-watcher-service: - pull_policy: never - image: content-watcher-service - build: - context: . - dockerfile: dev.Dockerfile - tags: - - content-watcher-service:latest - environment: - <<: *content-watcher-environment - ports: - - 3000:3000 - volumes: - - ./:/app - depends_on: - - redis - - frequency - - ipfs - - content-publishing-service-api - - content-publishing-service-worker - networks: - - content-watcher-service - -volumes: - redis_data: - ipfs_data: - chainstorage: - external: false - -networks: - content-watcher-service: diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index d4357592..ac8822d0 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -14,11 +14,6 @@ "generate:metadata": "set -a ; . ./env.template ; npx ts-node apps/api/src/generate-metadata.ts", "generate:swagger-ui": "set -a ; . ./env.template ; npx --yes @redocly/cli build-docs swagger.json --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", - "docker:build": "docker build -t content-watcher-service .", - "docker:build:dev": "docker-compose build", - "docker:run": " build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", - "docker:run:dev": "docker-compose up -d ; docker-compose logs", - "docker:stop:dev": "docker-compose stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest --coverage --verbose", diff --git a/services/graph/Dockerfile b/services/graph/Dockerfile deleted file mode 100644 index 5c152b81..00000000 --- a/services/graph/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Use a multi-stage build for efficiency -FROM node:20 AS builder - -WORKDIR /app - -COPY package*.json ./ - -RUN npm ci - -COPY . . - -# Build the application -RUN npm run build - -# Production stage -FROM node:20 - -WORKDIR /app - -COPY --from=builder /app/dist ./dist -COPY package*.json ./ -COPY ./lua ./lua -COPY ./scripts/docker-entrypoint.sh ./ -RUN chmod +x ./docker-entrypoint.sh - -RUN apt-get update && \ - apt-get install -y jq curl tini && \ - apt-get clean && \ - rm -rf /usr/share/doc /usr/share/man /usr/share/zsh - -RUN npm ci --omit=dev - -EXPOSE 3000 - -ENV START_PROCESS="api" - -ENTRYPOINT ["/usr/bin/tini", "--", "./docker-entrypoint.sh", "prod"] diff --git a/services/graph/dev.Dockerfile b/services/graph/dev.Dockerfile deleted file mode 100644 index 58fbf213..00000000 --- a/services/graph/dev.Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM node:20 - -RUN apt-get update && \ - apt-get install -y tini && \ - apt-get clean && \ - rm -rf /usr/share/doc /usr/share/man /usr/share/zsh - - -WORKDIR /app - -EXPOSE 3000 - -ENV START_PROCESS="api" - -VOLUME "/app" diff --git a/services/graph/docker-compose.yaml b/services/graph/docker-compose.yaml deleted file mode 100644 index 6a0f3fd9..00000000 --- a/services/graph/docker-compose.yaml +++ /dev/null @@ -1,94 +0,0 @@ -x-graph-service-environment: &graph-service-environment - FREQUENCY_URL: ws://frequency:9944 - REDIS_URL: redis://redis:6379 - QUEUE_HIGH_WATER: 1000 - API_PORT: 3000 - DEBOUNCE_SECONDS: 10 - CAPACITY_LIMIT: '{"type": "percentage", "value": 80 }' - GRAPH_ENVIRONMENT_TYPE: Mainnet - PROVIDER_ACCOUNT_SEED_PHRASE: '//Alice' - PROVIDER_ID: 1 - RECONNECTION_SERVICE_REQUIRED: false - WEBHOOK_FAILURE_THRESHOLD: 3 - WEBHOOK_RETRY_INTERVAL_SECONDS: 10 - HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 10 - HEALTH_CHECK_MAX_RETRIES: 4 - -services: - redis: - image: redis:latest - ports: - - 6379:6379 - volumes: - - redis_data:/data/redis - networks: - - graph-service - - frequency: - image: dsnp/instant-seal-node-with-deployed-schemas:latest - # We need to specify the platform because it's the only image - # built by Frequency at the moment, and auto-pull won't work otherwise - platform: linux/amd64 - # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. - # Other options you may want to add depending on your test scenario. - environment: - - SEALING_MODE=interval - - SEALING_INTERVAL=1 - # - CREATE_EMPTY_BLOCKS=true - # Uncomment below if you want to let the chain run and keep all of the historical blocks - # command: --state-pruning=archive - ports: - - 9944:9944 - networks: - - graph-service - volumes: - - chainstorage:/data - - graph-service-base: - pull_policy: never - image: graph-service-dev:latest - build: - context: . - dockerfile: dev.Dockerfile - tags: - - graph-service-dev:latest - - api: - pull_policy: never - image: graph-service-dev:latest - ports: - - 3000:3000 - command: ['npm', 'run', 'start:api:watch'] - environment: - <<: [*graph-service-environment] - volumes: - - ./:/app - depends_on: - - redis - - frequency - networks: - - graph-service - restart: on-failure - - worker: - image: graph-service-dev:latest - pull_policy: never - environment: - <<: [*graph-service-environment] - volumes: - - ./:/app - depends_on: - - redis - - frequency - - graph-service-base - networks: - - graph-service - restart: on-failure - -volumes: - redis_data: - chainstorage: - external: false - -networks: - graph-service: diff --git a/services/graph/package-lock.json b/services/graph/package-lock.json index 52d3e42a..b2563533 100644 --- a/services/graph/package-lock.json +++ b/services/graph/package-lock.json @@ -76,6 +76,7 @@ } }, "../../packages/ts-config": { + "name": "@amplica-labs/ts-config", "version": "1.0.0", "dev": true, "license": "Apache-2.0" diff --git a/services/graph/package.json b/services/graph/package.json index e3af6f3b..add42c7f 100644 --- a/services/graph/package.json +++ b/services/graph/package.json @@ -21,8 +21,6 @@ "start:worker:prod": "node dist/apps/worker/main.js", "start:worker:dev": "set -a ; . .env ; nest start worker", "start:worker:debug": "set -a ; . .env ;nest start worker --debug=9230 --watch", - "docker:build": "docker build -t graph-service .", - "docker:run": "docker build -t graph-service-deploy . ; docker run --env-file .env graph-service-deploy", "chain-setup": "tsx ./test-setup/main.ts", "clean": "rm -Rf dist", "lint": "eslint \"{apps,libs}/**/*.ts\" --fix", diff --git a/start-gateway.sh b/start-gateway.sh new file mode 100755 index 00000000..ec6ce2ea --- /dev/null +++ b/start-gateway.sh @@ -0,0 +1,189 @@ +#!/bin/bash +# Script to start all Gateway services on the Frequency Paseo Testnet + +# Function to ask for input with a default value and write to .env-saved +ask_and_save() { + local var_name=${1} + local prompt=${2} + local default_value=${3} + read -rp $'\n'"${prompt} [${default_value}]: " input + local value=${input:-$default_value} + echo "${var_name}=\"${value}\"" >> .env-saved +} + +# Check for Docker and Docker Compose +if ! command -v docker &> /dev/null || ! command -v docker compose &> /dev/null; then + printf "Docker and Docker Compose are required but not installed. Please install them and try again.\n" + exit 1 +fi + +# Load existing .env-saved file if it exists +if [ -f .env-saved ]; then + echo -e "Found saved environment from a previous run:\n" + cat .env-saved + echo + read -p "Do you want to re-use the saved paramters? [y/N]: " REUSE_SAVED + + if [[ ${REUSE_SAVED} =~ ^[Yy] ]] + then + cat << EOI +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Loading existing .env-saved file environment values... ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +EOI + else + cat << EOI +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Removing previous saved environment... | +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +EOI + rm .env-saved + fi +fi + +if [ ! -f .env-saved ] +then + # Setup some variables for easy port management + STARTING_PORT=3010 + for i in {0..10} + do + eval SERVICE_PORT_${i}=$(( STARTING_PORT + i )) + eval "export SERVICE_PORT_${i}=\${SERVICE_PORT_${i}}" + eval "echo SERVICE_PORT_${i}=\${SERVICE_PORT_${i}}" >> .env-saved + done + + # Create .env-saved file to store environment variables + cat << EOI +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Creating .env-saved file to store environment variables... ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +EOI + echo "COMPOSE_PROJECT_NAME='gateway-dev'" >> .env-saved + # Ask the user if they want to start on testnet or local + read -p "Do you want to start on Frequency Paseo Testnet [y/N]: " TESTNET_ENV + echo "TESTNET_ENV=\"$TESTNET_ENV\"" >> .env-saved + + if [[ $TESTNET_ENV =~ ^[Yy]$ ]] + then + cat << EOI + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Setting defaults for testnet... ┃ +┃ Hit to accept the default value or enter new value and then hit ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +EOI + DEFAULT_TESTNET_ENV="testnet" + DEFAULT_FREQUENCY_URL="wss://0.rpc.testnet.amplica.io" + DEFAULT_FREQUENCY_HTTP_URL="https://0.rpc.testnet.amplica.io" + DEFAULT_PROVIDER_ID="729" + DEFAULT_PROVIDER_ACCOUNT_SEED_PHRASE="DEFAULT seed phrase needed" + DEFAULT_IPFS_VOLUME="/data/ipfs" + else + echo -e "\nStarting on local..." + DEFAULT_TESTNET_ENV="local" + DEFAULT_FREQUENCY_URL="ws://frequency:9944" + DEFAULT_FREQUENCY_HTTP_URL="http://localhost:9944" + DEFAULT_PROVIDER_ID="1" + DEFAULT_PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" + DEFAULT_IPFS_VOLUME="/data/ipfs" + fi + DEFAULT_IPFS_ENDPOINT="http://ipfs:5001" + DEFAULT_IPFS_GATEWAY_URL='https://ipfs.io/ipfs/[CID]' + DEFAULT_IPFS_BASIC_AUTH_USER="" + DEFAULT_IPFS_BASIC_AUTH_SECRET="" + DEFAULT_IPFS_UA_GATEWAY_URL="http://localhost:8080" + DEFAULT_CONTENT_DB_VOLUME="content_db" + + + ask_and_save FREQUENCY_URL "Enter the Frequency Testnet RPC URL" "$DEFAULT_FREQUENCY_URL" + ask_and_save FREQUENCY_HTTP_URL "Enter the Frequency HTTP Testnet RPC URL" "$DEFAULT_FREQUENCY_HTTP_URL" +cat << EOI + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ 🔗💠📡 📡💠🔗 ┃ +┃ 🔗💠📡 A Provider is required to start the services. 📡💠🔗 ┃ +┃ 🔗💠📡 📡💠🔗 ┃ +┃ 🔗💠📡 If you need to become a provider, visit 📡💠🔗 ┃ +┃ 🔗💠📡 https://provider.frequency.xyz/ to get a Provider ID. 📡💠🔗 ┃ +┃ 🔗💠📡 📡💠🔗 ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +EOI + ask_and_save PROVIDER_ID "Enter Provider ID" "$DEFAULT_PROVIDER_ID" + ask_and_save PROVIDER_ACCOUNT_SEED_PHRASE "Enter Provider Seed Phrase" "$DEFAULT_PROVIDER_ACCOUNT_SEED_PHRASE" + cat << EOI + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ IPFS settings ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +EOI + read -p "Do you want to change the IPFS settings? [y/N]: " CHANGE_IPFS_SETTINGS + + if [[ $CHANGE_IPFS_SETTINGS =~ ^[Yy]$ ]] + then + ask_and_save IPFS_VOLUME "Enter the IPFS volume" "$DEFAULT_IPFS_VOLUME" + ask_and_save IPFS_ENDPOINT "Enter the IPFS Endpoint" "$DEFAULT_IPFS_ENDPOINT" + ask_and_save IPFS_GATEWAY_URL "Enter the IPFS Gateway URL" "$DEFAULT_IPFS_GATEWAY_URL" + ask_and_save IPFS_BASIC_AUTH_USER "Enter the IPFS Basic Auth User" "$DEFAULT_IPFS_BASIC_AUTH_USER" + ask_and_save IPFS_BASIC_AUTH_SECRET "Enter the IPFS Basic Auth Secret" "$DEFAULT_IPFS_BASIC_AUTH_SECRET" + ask_and_save IPFS_UA_GATEWAY_URL "Enter the browser-resolveable IPFS Gateway URL" "$DEFAULT_IPFS_UA_GATEWAY_URL" + else + cat >> .env-saved << EOI +IPFS_VOLUME="${DEFAULT_IPFS_VOLUME}" +IPFS_ENDPOINT="${DEFAULT_IPFS_ENDPOINT}" +IPFS_GATEWAY_URL="${DEFAULT_IPFS_GATEWAY_URL}" +IPFS_BASIC_AUTH_USER="${DEFAULT_IPFS_BASIC_AUTH_USER}" +IPFS_BASIC_AUTH_SECRET="${DEFAULT_IPFS_BASIC_AUTH_SECRET}" +IPFS_UA_GATEWAY_URL="${DEFAULT_IPFS_UA_GATEWAY_URL}" +EOI + fi +fi +set -a; source .env-saved; set +a + +if [[ ! $TESTNET_ENV =~ ^[Yy]$ ]] +then + # Start specific services in detached mode + echo -e "\nStarting local frequency services..." + docker compose up -d frequency + + # Wait for 15 seconds + echo "Waiting 15 seconds for Frequency to be ready..." + sleep 15 + + # Run npm run local:init + # echo "Running npm run local:init to provision Provider with capacity, etc..." + # cd backend && npm run local:init && cd .. +fi + +# Start all services in detached mode +echo -e "\nStarting all services..." +docker compose --profile backend up -d + +cat << EOI + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ 🚀 You can access the Gateway at the following local addresses: 🚀 ┃ +┃ * account-service: ┃ +┃ - API: http://localhost:${SERVICE_PORT_3} ┃ +┃ - Queue management: http://localhost:${SERVICE_PORT_3}/queues ┃ +┃ - Swagger UI: http://localhost:${SERVICE_PORT_3}/docs/swagger ┃ +┃ ┃ +┃ * content-publishing-service ┃ +┃ - API: http://localhost:${SERVICE_PORT_0} ┃ +┃ - Queue management: http://localhost:${SERVICE_PORT_0}/queues ┃ +┃ - Swagger UI: http://localhost:${SERVICE_PORT_0}/docs/swagger ┃ +┃ ┃ +┃ * content-watcher-service ┃ +┃ - API: http://localhost:${SERVICE_PORT_1} ┃ +┃ - Queue management: http://localhost:${SERVICE_PORT_1}/queues ┃ +┃ - Swagger UI: http://localhost:${SERVICE_PORT_1}/docs/swagger ┃ +┃ ┃ +┃ * graph-service ┃ +┃ - API: http://localhost:${SERVICE_PORT_2} ┃ +┃ - Queue management: http://localhost:${SERVICE_PORT_2}/queues ┃ +┃ - Swagger UI: http://localhost:${SERVICE_PORT_2}/docs/swagger ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +EOI diff --git a/stop-gateway.sh b/stop-gateway.sh new file mode 100755 index 00000000..eae7db39 --- /dev/null +++ b/stop-gateway.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Stop all services and optionally remove specified volumes to remove all state and start fresh + +# Export the variables that are used in the docker-compose.yaml file +if [ -f .env-saved ]; then + set -a; source .env-saved; set +a +fi + +# Shutting down any running services +echo "Shutting down any running services..." +docker compose --profile local-node --profile backend --profile frontend down + +# Ask the user if they want to remove specified volumes +read -p "Do you want to remove specified volumes to remove all state and start fresh? [y/N]: " REMOVE_VOLUMES + +if [[ $REMOVE_VOLUMES =~ ^[Yy]$ ]] +then + echo "Removing specified volumes..." + # Docker volume names are lowercase versions of the directory name + # In the root directory of the repository, we get from the system directory name + docker volume rm ${COMPOSE_PROJECT_NAME}_redis_data + docker volume rm ${COMPOSE_PROJECT_NAME}_ipfs_data + docker volume rm ${COMPOSE_PROJECT_NAME}_account_node_cache + docker volume rm ${COMPOSE_PROJECT_NAME}_graph_node_cache + docker volume rm ${COMPOSE_PROJECT_NAME}_content_publishing_node_cache + docker volume rm ${COMPOSE_PROJECT_NAME}_content_watcher_node_cache + if [[ ! $TESTNET_ENV =~ ^[Yy]$ ]] + then + docker volume rm ${COMPOSE_PROJECT_NAME}_chainstorage + fi +else + echo "Leaving Docker volumes alone." +fi From f3dc76463bfda967db1a162db8e253d9c6dac673 Mon Sep 17 00:00:00 2001 From: "V. Claire Olmstead" <43625033+claireolmstead@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:51:46 -0700 Subject: [PATCH 3/6] add ability to get account given public key (#325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Problem Currently, we cannot get an msa or any other account information given a public key. We need to add functionality to do so. Link to GitHub Issue(s): #266 # Solution - Added endpoint and service to support getting an account given a public key - Added a service to get an msaId given a public key ## Steps to Verify: 1. Spin up swagger 2. Test that endpoint return correct account. 3. See that open api displays all of the correct info. ## Screenshots (optional): Screenshot 2024-08-01 at 3 28 19 PM ## Additional Info While the ticket states to just return an MSA Id, I thought it would be more complete to return the associated account, which is an object that contains the msaId and handle. --------- Co-authored-by: Claire Olmstead --- .../controllers/v1/accounts-v1.controller.ts | 35 +- services/account/apps/api/src/metadata.ts | 42 +- .../apps/api/src/services/accounts.service.ts | 22 +- services/account/docs/index.html | 400 ++++++++++++++++++ .../src/types/dtos/accounts.response.dto.ts | 6 + services/account/swagger.json | 43 +- 6 files changed, 522 insertions(+), 26 deletions(-) create mode 100644 services/account/docs/index.html diff --git a/services/account/apps/api/src/controllers/v1/accounts-v1.controller.ts b/services/account/apps/api/src/controllers/v1/accounts-v1.controller.ts index 38950e6f..f0772780 100644 --- a/services/account/apps/api/src/controllers/v1/accounts-v1.controller.ts +++ b/services/account/apps/api/src/controllers/v1/accounts-v1.controller.ts @@ -40,12 +40,41 @@ export class AccountsControllerV1 { * @returns A promise that resolves to an Account object => {msaId, handle}. * @throws An error if the account cannot be found. */ - async getAccount(@Param('msaId') msaId: string): Promise { + async getAccountForMsa(@Param('msaId') msaId: string): Promise { try { this.logger.debug(`Received request to get account with msaId: ${msaId}`); - return await this.accountsService.getAccount(msaId); + const account = await this.accountsService.getAccount(msaId); + if (account) return account; + throw new HttpException('Failed to find the account', HttpStatus.NOT_FOUND); } catch (error) { this.logger.error(error); + if (error instanceof HttpException) throw error; + throw new HttpException('Failed to find the account', HttpStatus.BAD_REQUEST); + } + } + + @Get('account/:publicKey') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Fetch an account given a public key.' }) + @ApiOkResponse({ description: 'Found account', type: AccountResponse }) + /** + * Gets an account. + * @param queryParams - The query parameters for creating the account. + * @returns A promise that resolves to an Account object => {msaId, handle}. + * @throws An error if the msaId or account cannot be found. + */ + async getAccountForPublicKey(@Param('publicKey') publicKey: string): Promise { + try { + this.logger.debug(`Received request to get account with publicKey: ${publicKey}`); + const response = await this.accountsService.getMsaIdForPublicKey(publicKey); + if (response?.msaId) { + const account = await this.accountsService.getAccount(response.msaId); + if (account) return account; + } + throw new HttpException('Failed to find the account', HttpStatus.NOT_FOUND); + } catch (error) { + this.logger.error(error); + if (error instanceof HttpException) throw error; throw new HttpException('Failed to find the account', HttpStatus.BAD_REQUEST); } } @@ -58,7 +87,7 @@ export class AccountsControllerV1 { async postSignInWithFrequency(@Body() walletLoginRequest: WalletLoginRequestDto): Promise { try { this.logger.debug(`Received Sign-In With Frequency request: ${JSON.stringify(walletLoginRequest)}`); - return await this.accountsService.signInWithFrequency(walletLoginRequest); + return this.accountsService.signInWithFrequency(walletLoginRequest); } catch (error) { const errorMessage = 'Failed to Sign In With Frequency'; this.logger.error(`${errorMessage}: ${error}`); diff --git a/services/account/apps/api/src/metadata.ts b/services/account/apps/api/src/metadata.ts index 3e7f4e25..5f2d6760 100644 --- a/services/account/apps/api/src/metadata.ts +++ b/services/account/apps/api/src/metadata.ts @@ -4,13 +4,13 @@ export default async () => { ['../../../libs/common/src/types/dtos/wallet.login.request.dto']: await import( '../../../libs/common/src/types/dtos/wallet.login.request.dto' ), + ['../../../libs/common/src/types/dtos/accounts.response.dto']: await import( + '../../../libs/common/src/types/dtos/accounts.response.dto' + ), ['@polkadot/types-codec/primitive/U32']: await import('@polkadot/types-codec/primitive/U32'), ['../../../libs/common/src/types/dtos/wallet.login.config.response.dto']: await import( '../../../libs/common/src/types/dtos/wallet.login.config.response.dto' ), - ['../../../libs/common/src/types/dtos/accounts.response.dto']: await import( - '../../../libs/common/src/types/dtos/accounts.response.dto' - ), ['../../../libs/common/src/types/dtos/wallet.login.response.dto']: await import( '../../../libs/common/src/types/dtos/wallet.login.response.dto' ), @@ -91,6 +91,24 @@ export default async () => { }, }, ], + [ + import('../../../libs/common/src/types/dtos/accounts.response.dto'), + { + HandleResponseDTO: { + base_handle: { required: true, type: () => String }, + canonical_base: { required: true, type: () => String }, + suffix: { required: true, type: () => Number }, + }, + AccountResponse: { + msaId: { required: true, type: () => String }, + handle: { + required: false, + type: () => t['../../../libs/common/src/types/dtos/accounts.response.dto'].HandleResponseDTO, + }, + }, + MsaIdResponse: { msaId: { required: true, type: () => String } }, + }, + ], [ import('../../../libs/common/src/types/dtos/transaction.response.dto'), { TransactionResponse: { referenceId: { required: true, type: () => String } } }, @@ -105,15 +123,6 @@ export default async () => { }, }, ], - [ - import('../../../libs/common/src/types/dtos/accounts.response.dto'), - { - AccountResponse: { - msaId: { required: true, type: () => String }, - handle: { required: false, type: () => Object }, - }, - }, - ], [ import('../../../libs/common/src/types/dtos/wallet.login.config.response.dto'), { @@ -148,7 +157,12 @@ export default async () => { type: t['../../../libs/common/src/types/dtos/wallet.login.config.response.dto'] .WalletLoginConfigResponse, }, - getAccount: { type: t['../../../libs/common/src/types/dtos/accounts.response.dto'].AccountResponse }, + getAccountForMsa: { + type: t['../../../libs/common/src/types/dtos/accounts.response.dto'].AccountResponse, + }, + getAccountForPublicKey: { + type: t['../../../libs/common/src/types/dtos/accounts.response.dto'].AccountResponse, + }, postSignInWithFrequency: { type: t['../../../libs/common/src/types/dtos/wallet.login.response.dto'].WalletLoginResponse, }, @@ -175,7 +189,7 @@ export default async () => { changeHandle: { type: t['../../../libs/common/src/types/dtos/transaction.response.dto'].TransactionResponse, }, - getHandle: { type: Object }, + getHandle: { type: t['../../../libs/common/src/types/dtos/accounts.response.dto'].HandleResponseDTO }, }, }, ], diff --git a/services/account/apps/api/src/services/accounts.service.ts b/services/account/apps/api/src/services/accounts.service.ts index 40ad0490..c75bba3f 100644 --- a/services/account/apps/api/src/services/accounts.service.ts +++ b/services/account/apps/api/src/services/accounts.service.ts @@ -6,7 +6,7 @@ import { ConfigService } from '#lib/config/config.service'; import { EnqueueService } from '#lib/services/enqueue-request.service'; import { WalletLoginRequestDto, PublishSIWFSignupRequest } from '#lib/types/dtos/wallet.login.request.dto'; import { WalletLoginResponse } from '#lib/types/dtos/wallet.login.response.dto'; -import { AccountResponse } from '#lib/types/dtos/accounts.response.dto'; +import { AccountResponse, MsaIdResponse } from '#lib/types/dtos/accounts.response.dto'; import { WalletLoginConfigResponse } from '#lib/types/dtos/wallet.login.config.response.dto'; @Injectable() @@ -21,7 +21,7 @@ export class AccountsService { this.logger = new Logger(this.constructor.name); } - async getAccount(msaId: string): Promise { + async getAccount(msaId: string): Promise { try { const isValidMsaId = await this.blockchainService.isValidMsaId(msaId); if (isValidMsaId) { @@ -33,13 +33,29 @@ export class AccountsService { this.logger.log(`Failed to get handle for msaId: ${msaId}`); return { msaId }; } - throw new Error(`Invalid msaId: ${msaId}`); + this.logger.log(`Invalid msaId: ${msaId}`); + return null; } catch (e) { this.logger.error(`Error during get account request: ${e}`); throw new Error('Failed to get account'); } } + async getMsaIdForPublicKey(publicKey: string): Promise { + try { + const msaId = await this.blockchainService.publicKeyToMsaId(publicKey); + if (msaId) { + this.logger.debug(`Found msaId: ${msaId} for public key: ${publicKey}`); + return { msaId }; + } + this.logger.debug(`Did not find msaId for public key: ${publicKey}`); + return null; + } catch (e) { + this.logger.error(`Error during get msaId request: ${e}`); + throw new Error('Failed to get msaId'); + } + } + async getSIWFConfig(): Promise { let response: WalletLoginConfigResponse; try { diff --git a/services/account/docs/index.html b/services/account/docs/index.html new file mode 100644 index 00000000..f66aa172 --- /dev/null +++ b/services/account/docs/index.html @@ -0,0 +1,400 @@ + + + + + + Account Service + + + + + + + + + +

Account Service (1.0)

Download OpenAPI specification:Download

Account Service API

+

v1/accounts

Get the Sign-In With Frequency Configuration

Responses

Response samples

Content type
application/json
{
  • "providerId": "string",
  • "siwfUrl": "string",
  • "frequencyRpcUrl": "string"
}

Request to sign in with Frequency

Request Body schema: application/json
required
object

The wallet login request information

+
object (SignUpResponseDto)

Responses

Request samples

Content type
application/json
{
  • "signIn": {
    },
  • "signUp": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string",
  • "msaId": "string",
  • "publicKey": "string"
}

Fetch an account given an msaId.

path Parameters
msaId
required
string

Responses

Response samples

Content type
application/json
{
  • "msaId": "string",
  • "handle": {
    }
}

Fetch an account given a public key.

path Parameters
publicKey
required
string

Responses

Response samples

Content type
application/json
{
  • "msaId": "string",
  • "handle": {
    }
}

v1/delegation

Get the delegation information associated with an msaId.

path Parameters
msaId
required
string

Responses

v1/handles

Request to create a new handle for an account

Request Body schema: application/json
required
accountId
required
string
required
object (HandlePayload)
proof
required
string

Responses

Request samples

Content type
application/json
{
  • "accountId": "string",
  • "payload": {
    },
  • "proof": "string"
}

Request to change a handle

Request Body schema: application/json
required
accountId
required
string
required
object (HandlePayload)
proof
required
string

Responses

Request samples

Content type
application/json
{
  • "accountId": "string",
  • "payload": {
    },
  • "proof": "string"
}

Fetch a handle given an msaId.

path Parameters
msaId
required
string

Responses

v1/keys

add new control keys for an MSA ID

Request Body schema: application/json
required
msaOwnerAddress
required
string
msaOwnerSignature
required
string
newKeyOwnerSignature
required
string
required
object (KeysRequestPayload)

Responses

Request samples

Content type
application/json
{
  • "msaOwnerAddress": "string",
  • "msaOwnerSignature": "string",
  • "newKeyOwnerSignature": "string",
  • "payload": {
    }
}

Fetch public keys given an msaId.

path Parameters
msaId
required
string

Responses

health

Check the health status of the service

Responses

Check the live status of the service

Responses

Check the ready status of the service

Responses

+ + + + diff --git a/services/account/libs/common/src/types/dtos/accounts.response.dto.ts b/services/account/libs/common/src/types/dtos/accounts.response.dto.ts index 68c77b45..db5fcd2d 100644 --- a/services/account/libs/common/src/types/dtos/accounts.response.dto.ts +++ b/services/account/libs/common/src/types/dtos/accounts.response.dto.ts @@ -22,3 +22,9 @@ export class AccountResponse { @IsOptional() handle?: HandleResponseDTO; } + +export class MsaIdResponse { + @ApiProperty() + @IsNotEmpty() + msaId: string; +} diff --git a/services/account/swagger.json b/services/account/swagger.json index d0f7799a..3fd24a60 100644 --- a/services/account/swagger.json +++ b/services/account/swagger.json @@ -55,7 +55,7 @@ }, "/v1/accounts/{msaId}": { "get": { - "operationId": "AccountsControllerV1_getAccount", + "operationId": "AccountsControllerV1_getAccountForMsa", "summary": "Fetch an account given an msaId.", "parameters": [ { @@ -84,6 +84,37 @@ ] } }, + "/v1/accounts/account/{publicKey}": { + "get": { + "operationId": "AccountsControllerV1_getAccountForPublicKey", + "summary": "Fetch an account given a public key.", + "parameters": [ + { + "name": "publicKey", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Found account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountResponse" + } + } + } + } + }, + "tags": [ + "v1/accounts" + ] + } + }, "/v1/delegation/{msaId}": { "get": { "operationId": "DelegationControllerV1_getDelegation", @@ -359,7 +390,7 @@ "type": "string" }, "signature": { - "type": "object" + "type": "string" } }, "required": [ @@ -399,7 +430,7 @@ "type": "string" }, "encodedExtrinsic": { - "type": "object" + "type": "string" } }, "required": [ @@ -489,7 +520,7 @@ "$ref": "#/components/schemas/HandlePayload" }, "proof": { - "type": "object" + "type": "string" } }, "required": [ @@ -524,10 +555,10 @@ "type": "string" }, "msaOwnerSignature": { - "type": "object" + "type": "string" }, "newKeyOwnerSignature": { - "type": "object" + "type": "string" }, "payload": { "$ref": "#/components/schemas/KeysRequestPayload" From a166a97796d70d953c105a8fbbba49bd608f83e7 Mon Sep 17 00:00:00 2001 From: Matthew Orris <1466844+mattheworris@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:55:27 -0400 Subject: [PATCH 4/6] [bug] content-publisher needs to be its own source of truth for announcement attachments (#341) # Description As a user, I should not have to keep track of the association between an uploaded asset reference ID and its media type. More specifically, I should not be able to tell content-publisher the type of an asset in an Announcment publish request (Broadcast, Reply, etc), as that opens up an attack vector to specify a media type that does not match the actual uploaded asset. content-publisher should be its own source of truth in coordinating asset type with the actual asset. - [x] Remove `type` from `BroadcastDto`, `UpdateDto`, `ReplyDto` - Removed from `AssetDto`, which is included by all of the above - [x] Update OpenAPI spec - Updated using the `generate` scripts - [x] Add `type` to cached metadata for uploaded asset Closes #338 --- services/content-publishing/README.md | 13 +- .../apps/api/src/api.service.ts | 66 ++++----- .../apps/api/src/metadata.ts | 97 +++++++++++- .../dsnp.announcement.processor.ts | 139 +++++++----------- .../libs/common/src/dtos/activity.dto.ts | 9 +- .../src/interfaces/asset-job.interface.ts | 3 + .../src/interfaces/request-job.interface.ts | 9 +- services/content-publishing/swagger.json | 14 +- 8 files changed, 187 insertions(+), 163 deletions(-) diff --git a/services/content-publishing/README.md b/services/content-publishing/README.md index e92a46da..b45229bf 100644 --- a/services/content-publishing/README.md +++ b/services/content-publishing/README.md @@ -167,27 +167,16 @@ Use the provided [env.template](./env.template) file to create an initial enviro ```sh cp env.template .env - cp env.template .env.docker.dev ``` 2. Configure the environment variable values according to your environment. -### Setup - -Clone this repository to your desired folder: - -Example commands: - -```sh - git clone git@github.com:AmplicaLabs/content-publishing-service.git - cd content-publishing-service -``` - ### Install Install NPM Dependencies: ```sh + cd services/content-publishing npm install ``` diff --git a/services/content-publishing/apps/api/src/api.service.ts b/services/content-publishing/apps/api/src/api.service.ts index 46868078..b1aae1e3 100644 --- a/services/content-publishing/apps/api/src/api.service.ts +++ b/services/content-publishing/apps/api/src/api.service.ts @@ -6,15 +6,8 @@ import { BulkJobOptions } from 'bullmq/dist/esm/interfaces'; import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util'; -import { - AnnouncementTypeDto, - RequestTypeDto, - AnnouncementResponseDto, - AssetIncludedRequestDto, - isImage, - UploadResponseDto, -} from '#libs/dtos'; -import { IRequestJob, IAssetMetadata, IAssetJob } from '#libs/interfaces'; +import { AnnouncementTypeDto, RequestTypeDto, AnnouncementResponseDto, AssetIncludedRequestDto, isImage, UploadResponseDto, AttachmentType } from '#libs/dtos'; +import { IRequestJob, IAssetMetadata, IAssetJob, IAssetTypeInfo } from '#libs/interfaces'; import { REQUEST_QUEUE_NAME, ASSET_QUEUE_NAME } from '#libs/queues/queue.constants'; import { calculateIpfsCID } from '#libs/utils/ipfs'; import { getAssetMetadataKey, getAssetDataKey, STORAGE_EXPIRE_UPPER_LIMIT_SECONDS } from '#libs/utils/redis'; @@ -35,7 +28,7 @@ export class ApiService { announcementType: AnnouncementTypeDto, dsnpUserId: string, content: RequestTypeDto, - assetToMimeType?: Map, + assetToMimeType?: IRequestJob['assetToMimeType'], ): Promise { const data = { content, @@ -60,12 +53,10 @@ export class ApiService { }; } - async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise | undefined> { + async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise { const checkingList: { onlyImage: boolean; referenceId: string }[] = []; if (content.profile) { - content.profile.icon?.forEach((reference) => - checkingList.push({ onlyImage: true, referenceId: reference.referenceId }), - ); + content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId })); } else if (content.content) { content.content.assets?.forEach((asset) => asset.references?.forEach((reference) => @@ -77,19 +68,15 @@ export class ApiService { ); } - const redisResults = await Promise.all( - checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId))), - ); + const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId)))); const errors: string[] = []; const map = new Map(); redisResults.forEach((res, index) => { if (res === null) { - errors.push( - `${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`, - ); + errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`); } else { const metadata: IAssetMetadata = JSON.parse(res); - map[checkingList[index].referenceId] = metadata.mimeType; + map[checkingList[index].referenceId] = { mimeType: metadata.mimeType, attachmentType: metadata.type }; // checks if attached asset is an image if (checkingList[index].onlyImage && !isImage(metadata.mimeType)) { @@ -114,20 +101,29 @@ export class ApiService { const jobs: any[] = []; files.forEach((f, index) => { // adding data and metadata to the transaction - dataTransaction = dataTransaction.setex( - getAssetDataKey(references[index]), - STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, - f.buffer, - ); - metadataTransaction = metadataTransaction.setex( - getAssetMetadataKey(references[index]), - STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, - JSON.stringify({ - ipfsCid: references[index], - mimeType: f.mimetype, - createdOn: Date.now(), - } as IAssetMetadata), - ); + dataTransaction = dataTransaction.setex(getAssetDataKey(references[index]), STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, f.buffer); + const type = ((m) => { + switch (m) { + case 'image': + return AttachmentType.IMAGE; + case 'audio': + return AttachmentType.AUDIO; + case 'video': + return AttachmentType.VIDEO; + default: + throw new Error('Invalid MIME type'); + } + })(f.mimetype.split('/')[0]); + + const assetCache: IAssetMetadata = { + ipfsCid: references[index], + mimeType: f.mimetype, + createdOn: Date.now(), + type: type, + }; + + metadataTransaction = metadataTransaction.setex(getAssetMetadataKey(references[index]), STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, JSON.stringify(assetCache)); + // adding asset job to the jobs jobs.push({ name: `Asset Job - ${references[index]}`, diff --git a/services/content-publishing/apps/api/src/metadata.ts b/services/content-publishing/apps/api/src/metadata.ts index bf1f6e63..82bb0bd6 100644 --- a/services/content-publishing/apps/api/src/metadata.ts +++ b/services/content-publishing/apps/api/src/metadata.ts @@ -1,8 +1,93 @@ /* eslint-disable */ export default async () => { - const t = { - ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), - ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto") - }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }]] } }; -}; \ No newline at end of file + const t = { + ['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'), + ['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'), + }; + return { + '@nestjs/swagger': { + models: [ + [ + import('../../../libs/common/src/dtos/common.dto'), + { + DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } }, + AnnouncementResponseDto: { referenceId: { required: true, type: () => String } }, + UploadResponseDto: { assetIds: { required: true, type: () => [String] } }, + FilesUploadDto: { files: { required: true, type: () => [Object] } }, + }, + ], + [ + import('../../../libs/common/src/dtos/activity.dto'), + { + LocationDto: { + name: { required: true, type: () => String, minLength: 1 }, + accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, + altitude: { required: false, type: () => Number }, + latitude: { required: false, type: () => Number }, + longitude: { required: false, type: () => Number }, + radius: { required: false, type: () => Number, minimum: 0 }, + units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto }, + }, + AssetReferenceDto: { + referenceId: { required: true, type: () => String, minLength: 1 }, + height: { required: false, type: () => Number, minimum: 1 }, + width: { required: false, type: () => Number, minimum: 1 }, + duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' }, + }, + TagDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto }, + name: { required: false, type: () => String, minLength: 1 }, + mentionedId: { required: false, type: () => String }, + }, + AssetDto: { + references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + name: { required: false, type: () => String, minLength: 1 }, + href: { required: false, type: () => String, minLength: 1 }, + }, + BaseActivityDto: { + name: { required: false, type: () => String }, + tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] }, + location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto }, + }, + NoteActivityDto: { + content: { required: true, type: () => String, minLength: 1 }, + published: { required: true, type: () => String }, + assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] }, + }, + ProfileActivityDto: { + icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + summary: { required: false, type: () => String }, + published: { required: false, type: () => String }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/announcement.dto'), + { + BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } }, + ReplyDto: { + inReplyTo: { required: true, type: () => String }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + TombstoneDto: { + targetContentHash: { required: true, type: () => String }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + }, + UpdateDto: { + targetContentHash: { required: true, type: () => String }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + ReactionDto: { + emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' }, + apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, + inReplyTo: { required: true, type: () => String }, + }, + ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } }, + }, + ], + ], + controllers: [[import('./controllers/health.controller'), { HealthController: { healthz: {}, livez: {}, readyz: {} } }]], + }, + }; +}; diff --git a/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts index 296fcd0f..f7a4064b 100644 --- a/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ b/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -24,8 +24,9 @@ import { TombstoneDto, ModifiableAnnouncementTypeDto, TagTypeDto, - AttachmentTypeDto, + AttachmentType, AssetDto, + TagDto, } from '#libs/dtos'; import { IRequestJob, @@ -43,14 +44,7 @@ import { ProfileAnnouncement, createProfile, } from '#libs/interfaces'; -import { - BROADCAST_QUEUE_NAME, - REPLY_QUEUE_NAME, - REACTION_QUEUE_NAME, - UPDATE_QUEUE_NAME, - PROFILE_QUEUE_NAME, - TOMBSTONE_QUEUE_NAME, -} from '#libs/queues/queue.constants'; +import { BROADCAST_QUEUE_NAME, REPLY_QUEUE_NAME, REACTION_QUEUE_NAME, UPDATE_QUEUE_NAME, PROFILE_QUEUE_NAME, TOMBSTONE_QUEUE_NAME } from '#libs/queues/queue.constants'; import { calculateDsnpHash } from '#libs/utils/ipfs'; import { IpfsService } from '#libs/utils/ipfs.client'; @@ -132,16 +126,8 @@ export class DsnpAnnouncementProcessor { private async queueUpdate(data: IRequestJob) { const updateDto = data.content as UpdateDto; - const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType( - updateDto.targetAnnouncementType, - ); - const update = await this.processUpdate( - updateDto, - updateAnnouncementType, - updateDto.targetContentHash ?? '', - data.dsnpUserId, - data.assetToMimeType, - ); + const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(updateDto.targetAnnouncementType); + const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId, data.assetToMimeType); await this.updateQueue.add(`Update Job - ${data.id}`, update, { jobId: data.id, removeOnFail: false, @@ -160,9 +146,7 @@ export class DsnpAnnouncementProcessor { private async queueTombstone(data: IRequestJob) { const tombStoneDto = data.content as TombstoneDto; - const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType( - tombStoneDto.targetAnnouncementType, - ); + const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(tombStoneDto.targetAnnouncementType); const tombstone = createTombstone(data.dsnpUserId, announcementType, tombStoneDto.targetContentHash ?? ''); await this.tombstoneQueue.add(`Tombstone Job - ${data.id}`, tombstone, { jobId: data.id, @@ -171,9 +155,7 @@ export class DsnpAnnouncementProcessor { }); } - private async getAnnouncementTypeFromModifiableAnnouncementType( - modifiableAnnouncementType: ModifiableAnnouncementTypeDto, - ): Promise { + private async getAnnouncementTypeFromModifiableAnnouncementType(modifiableAnnouncementType: ModifiableAnnouncementTypeDto): Promise { this.logger.debug(`Getting announcement type from modifiable announcement type`); switch (modifiableAnnouncementType) { case ModifiableAnnouncementTypeDto.BROADCAST: @@ -185,13 +167,10 @@ export class DsnpAnnouncementProcessor { } } - public async prepareNote(noteContent: any, assetToMimeType?: Map): Promise<[string, string, string]> { - this.logger.debug(`Preparing note`); + public async prepareNote(noteContent: BroadcastDto | ReplyDto | UpdateDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise<[string, string, string]> { + this.logger.debug(`Preparing note of type: ${typeof noteContent.content}`); const tags: ActivityContentTag[] = this.prepareTags(noteContent?.content.tag); - const attachments: ActivityContentAttachment[] = await this.prepareAttachments( - noteContent.content.assets, - assetToMimeType, - ); + const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent.content.assets, assetToMimeType); const note = createNote(noteContent.content.content ?? '', new Date(noteContent.content.published ?? ''), { name: noteContent.content.name, @@ -205,16 +184,22 @@ export class DsnpAnnouncementProcessor { return [cid, ipfsUrl, hash]; } - private prepareTags(tagData?: any[]): ActivityContentTag[] { + private prepareTags(tagData?: TagDto[]): ActivityContentTag[] { this.logger.debug(`Preparing tags`); const tags: ActivityContentTag[] = []; if (tagData) { tagData.forEach((tag) => { switch (tag.type) { case TagTypeDto.Hashtag: + if (!tag.name) { + throw new Error(`Tag name is required`); + } tags.push({ name: tag.name }); break; case TagTypeDto.Mention: + if (!tag.mentionedId) { + throw new Error(`Mentioned ID is required`); + } tags.push({ name: tag.name, type: 'Mention', @@ -229,33 +214,38 @@ export class DsnpAnnouncementProcessor { return tags; } - private async prepareAttachments( - assetData?: any[], - assetToMimeType?: Map, - ): Promise { + private async prepareAttachments(assetData?: AssetDto[], assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const attachments: ActivityContentAttachment[] = []; if (assetData) { const promises = assetData.map(async (asset) => { - switch (asset.type) { - case AttachmentTypeDto.LINK: - attachments.push(this.prepareLinkAttachment(asset)); - break; - case AttachmentTypeDto.IMAGE: - attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.VIDEO: - attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.AUDIO: - attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); - break; - default: - throw new Error(`Unsupported attachment type ${typeof asset.type}`); + if (asset.references) { + const assetPromises = asset.references.map(async (reference) => { + if (!assetToMimeType) { + throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); + } + const { attachmentType } = assetToMimeType[reference.referenceId]; + switch (attachmentType) { + case AttachmentType.LINK: + attachments.push(this.prepareLinkAttachment(asset)); + break; + case AttachmentType.IMAGE: + attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); + break; + case AttachmentType.VIDEO: + attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); + break; + case AttachmentType.AUDIO: + attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); + break; + default: + throw new Error(`Unsupported attachment type ${attachmentType}`); + } + }); + await Promise.all(assetPromises); } }); await Promise.all(promises); } - return attachments; } @@ -268,10 +258,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareImageAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareImageAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const imageLinks: ActivityContentImageLink[] = []; if (asset.references) { const promises = asset.references.map(async (reference) => { @@ -301,10 +288,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareVideoAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareVideoAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const videoLinks: ActivityContentVideoLink[] = []; let duration: string | undefined = ''; @@ -338,10 +322,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareAudioAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareAudioAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const audioLinks: ActivityContentAudioLink[] = []; let duration = ''; if (asset.references) { @@ -373,21 +354,13 @@ export class DsnpAnnouncementProcessor { }; } - private async processBroadcast( - content: BroadcastDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processBroadcast(content: BroadcastDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing broadcast`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createBroadcast(dsnpUserId, ipfsUrl, hash); } - private async processReply( - content: ReplyDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processReply(content: ReplyDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing reply for ${content.inReplyTo}`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createReply(dsnpUserId, ipfsUrl, hash, content.inReplyTo); @@ -403,23 +376,16 @@ export class DsnpAnnouncementProcessor { targetAnnouncementType: AnnouncementType, targetContentHash: string, dsnpUserId: string, - assetToMimeType?: Map, + assetToMimeType?: IRequestJob['assetToMimeType'], ): Promise { this.logger.debug(`Processing update`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createUpdate(dsnpUserId, ipfsUrl, hash, targetAnnouncementType, targetContentHash); } - private async processProfile( - content: ProfileDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processProfile(content: ProfileDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing profile`); - const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments( - content.profile.icon ?? [], - assetToMimeType, - ); + const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? [], assetToMimeType); const profileActivity: ActivityContentProfile = { '@context': 'https://www.w3.org/ns/activitystreams', @@ -436,10 +402,7 @@ export class DsnpAnnouncementProcessor { return createProfile(dsnpUserId, this.formIpfsUrl(cid), hash); } - private async prepareProfileIconAttachments( - icons: any[], - assetToMimeType?: Map, - ): Promise { + private async prepareProfileIconAttachments(icons: any[], assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const attachments: ActivityContentImageLink[] = []; const promises = icons.map(async (icon) => { diff --git a/services/content-publishing/libs/common/src/dtos/activity.dto.ts b/services/content-publishing/libs/common/src/dtos/activity.dto.ts index 053cb24c..668b6df2 100644 --- a/services/content-publishing/libs/common/src/dtos/activity.dto.ts +++ b/services/content-publishing/libs/common/src/dtos/activity.dto.ts @@ -43,7 +43,7 @@ export enum TagTypeDto { } // eslint-disable-next-line no-shadow -export enum AttachmentTypeDto { +export enum AttachmentType { LINK = 'link', IMAGE = 'image', AUDIO = 'audio', @@ -121,10 +121,7 @@ export class TagDto { } export class AssetDto { - @IsEnum(AttachmentTypeDto) - type: AttachmentTypeDto; - - @ValidateIf((o) => o.type !== AttachmentTypeDto.LINK) + @ValidateIf((o) => o.type !== AttachmentType.LINK) @ValidateNested({ each: true }) @IsArray() @ArrayNotEmpty() @@ -137,7 +134,7 @@ export class AssetDto { @MinLength(1) name?: string; - @ValidateIf((o) => o.type === AttachmentTypeDto.LINK) + @ValidateIf((o) => o.type === AttachmentType.LINK) @IsString() @MinLength(1) @IsUrl({ protocols: ['http', 'https'] }) diff --git a/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts b/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts index 3da2dc91..cf38549b 100644 --- a/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts +++ b/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts @@ -1,3 +1,5 @@ +import { AttachmentType } from '#libs/dtos'; + export interface IAssetJob { ipfsCid: string; mimeType: string; @@ -9,4 +11,5 @@ export interface IAssetMetadata { ipfsCid: string; mimeType: string; createdOn: number; + type: AttachmentType; } diff --git a/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts b/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts index c2822a92..98577b7d 100644 --- a/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts @@ -1,10 +1,13 @@ -import { AnnouncementTypeDto, RequestTypeDto } from '#libs/dtos'; - +import { AnnouncementTypeDto, AttachmentType, RequestTypeDto } from '#libs/dtos'; +export interface IAssetTypeInfo { + mimeType: string; + attachmentType: AttachmentType; +} export interface IRequestJob { id: string; announcementType: AnnouncementTypeDto; dsnpUserId: string; - assetToMimeType?: Map; + assetToMimeType?: Map; content?: RequestTypeDto; dependencyAttempt: number; } diff --git a/services/content-publishing/swagger.json b/services/content-publishing/swagger.json index f6aa53fc..99100aec 100644 --- a/services/content-publishing/swagger.json +++ b/services/content-publishing/swagger.json @@ -403,15 +403,6 @@ "AssetDto": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "link", - "image", - "audio", - "video" - ] - }, "references": { "type": "array", "items": { @@ -426,10 +417,7 @@ "type": "string", "minLength": 1 } - }, - "required": [ - "type" - ] + } }, "TagDto": { "type": "object", From eeb345f370652c5dea8aeb25ec8ce56ee67bec5c Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Tue, 6 Aug 2024 10:39:22 -0400 Subject: [PATCH 5/6] fix: regex for release tag (#342) Fixes the regex used to validate the release tag for the CI pipeline. Was having trouble with a service name containing an embedded hyphen, like `content-publishing` or `content-watcher`. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e044ce76..852c09c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: run: | valid=false test_run=false - ENTIRE_TAG_RE="^(([[:alpha:]]+)-)?(.*)$" + ENTIRE_TAG_RE="^(([[:alpha:]\-]+)-)?(.*)$" VERSION_TAG_RE="^v[\.]?([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(-[[:alpha:][:digit:]\.]+)*)$" TAG="${{ github.event.release.tag_name }}" echo "Running with tag: ${TAG}" From ff4539b04a4548c7157bd61cf5fa21e6ab024b58 Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Tue, 6 Aug 2024 16:10:52 -0400 Subject: [PATCH 6/6] Chore: Load Tests run in CI (#324) # Problem We want to run the k6 tests in CI so that they also function as semi-integration test system. Closes: #317 # Solution - Added a workflow for the k6 tests - Disabled one test I didn't want to get working without additional work. So added an issue for that: https://github.com/ProjectLibertyLabs/gateway/issues/344 --- .github/workflows/load-tests.yml | 106 +++ docker-compose-k6.account.yaml | 71 ++ docker-compose-k6.content-publishing.yaml | 45 + docker-compose-k6.content-watcher.yaml | 36 + docker-compose-k6.graph.yaml | 43 + docker-compose.yaml | 5 +- .../account/docs/account_service_arch.drawio | 2 +- services/account/k6-test/README.md | 12 +- ...oad.js => account-service-load.k6.skip.js} | 64 +- .../k6-test/generate/helpers/chain.mjs | 26 + services/account/k6-test/generate/signups.mjs | 69 ++ .../{health-check.js => health-check.k6.js} | 2 +- .../{new-sign-up.js => new-sign-up.k6.js} | 52 +- services/account/k6-test/package-lock.json | 843 ++++++++++++++++- services/account/k6-test/package.json | 9 +- services/account/k6-test/signups.gen.js | 165 ++++ services/account/package.json | 4 +- .../account/rust-webhook-server/src/main.rs | 2 +- services/content-publishing/k6-test/README.md | 2 +- .../k6-test/health-check.k6.js | 41 + .../k6-test/{script.js => script.k6.js} | 0 services/content-publishing/package.json | 2 +- .../apps/api/test/app.e2e-spec.ts | 2 +- services/content-watcher/k6-test/README.md | 25 + .../k6-test/health-check.k6.js | 41 + .../content-watcher/k6-test/package-lock.json | 22 + services/content-watcher/k6-test/package.json | 15 + services/content-watcher/package.json | 2 +- services/graph/k6-test/README.md | 2 +- services/graph/k6-test/health-check.k6.js | 41 + services/graph/package.json | 2 +- tools/ci-k6/main.mjs | 52 ++ tools/ci-k6/package-lock.json | 852 ++++++++++++++++++ tools/ci-k6/package.json | 16 + 34 files changed, 2566 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/load-tests.yml create mode 100644 docker-compose-k6.account.yaml create mode 100644 docker-compose-k6.content-publishing.yaml create mode 100644 docker-compose-k6.content-watcher.yaml create mode 100644 docker-compose-k6.graph.yaml rename services/account/k6-test/{account-service-load.js => account-service-load.k6.skip.js} (74%) create mode 100644 services/account/k6-test/generate/helpers/chain.mjs create mode 100644 services/account/k6-test/generate/signups.mjs rename services/account/k6-test/{health-check.js => health-check.k6.js} (95%) rename services/account/k6-test/{new-sign-up.js => new-sign-up.k6.js} (65%) create mode 100644 services/account/k6-test/signups.gen.js create mode 100644 services/content-publishing/k6-test/health-check.k6.js rename services/content-publishing/k6-test/{script.js => script.k6.js} (100%) create mode 100644 services/content-watcher/k6-test/README.md create mode 100644 services/content-watcher/k6-test/health-check.k6.js create mode 100644 services/content-watcher/k6-test/package-lock.json create mode 100644 services/content-watcher/k6-test/package.json create mode 100644 services/graph/k6-test/health-check.k6.js create mode 100644 tools/ci-k6/main.mjs create mode 100644 tools/ci-k6/package-lock.json create mode 100644 tools/ci-k6/package.json diff --git a/.github/workflows/load-tests.yml b/.github/workflows/load-tests.yml new file mode 100644 index 00000000..1684e1dd --- /dev/null +++ b/.github/workflows/load-tests.yml @@ -0,0 +1,106 @@ +name: Load Tests + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + determine-service-matrix: + runs-on: ubuntu-latest + outputs: + services: ${{ steps.determine-matrix.outputs.changes }} + steps: + - name: Check Out Repo + uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: determine-matrix + with: + # Note, 'common' is for future use--when code is factored out into common libraries, + # but it's set up here so that the workflow logic could be tested with it present + filters: | + common: + - '*' + - '.github/**' + account: + - '*' + - '.github/**' + - 'services/account/**' + graph: + - '*' + - '.github/**' + - 'services/graph/**' + content-publishing: + - '*' + - '.github/**' + - 'services/content-publishing/**' + content-watcher: + - '*' + - '.github/**' + - 'services/content-watcher/**' + + build: + name: '[${{ matrix.service }}] Load Test' + runs-on: ubuntu-latest + needs: determine-service-matrix + strategy: + fail-fast: false + matrix: + service: ${{ fromJson(needs.determine-service-matrix.outputs.services) }} + exclude: + - service: common + steps: + - uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: tools/ci-k6/package-lock.json + - name: Install dependencies + working-directory: tools/ci-k6 + run: npm ci + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{secrets.DOCKERHUB_USERNAME}} + password: ${{secrets.DOCKERHUB_TOKEN}} + + - name: Start Frequency + run: | + docker compose -f docker-compose.yaml -f docker-compose-k6.${{ matrix.service }}.yaml up -d frequency + sleep 5 + - name: Generate Provider and Capacity + working-directory: tools/ci-k6 + run: npm run main + + # Just start the services we need... + - name: Start All Services + run: docker compose -f docker-compose.yaml -f docker-compose-k6.${{ matrix.service }}.yaml up -d + + - name: Wait for API to be ready + uses: cygnetdigital/wait_for_response@v2.0.0 + with: + url: 'http://localhost:3000/readyz' + responseCode: '200' + timeout: 120000 + interval: 2000 + + - name: Setup K6 + uses: grafana/setup-k6-action@v1 + - name: Run k6 Tests + run: | + for test_file in services/${{ matrix.service }}/k6-test/*.k6.js; do + k6 run --no-color --quiet --no-usage-report "$test_file" || exit 1 + done + - name: Stop Docker Compose + if: always() + run: docker compose -f docker-compose.yaml -f docker-compose-k6.${{ matrix.service }}.yaml down diff --git a/docker-compose-k6.account.yaml b/docker-compose-k6.account.yaml new file mode 100644 index 00000000..f935d6c6 --- /dev/null +++ b/docker-compose-k6.account.yaml @@ -0,0 +1,71 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json + +services: + account-service-api: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.account + command: ['api'] + ports: + - 3000:3000 + volumes: !reset [] + depends_on: !override + redis: + condition: service_started + ipfs: + condition: service_healthy + account-service-webhook: + condition: service_healthy + environment: + PROVIDER_BASE_URL: 'http://account-service-webhook:3001/webhooks/account-service' + WEBHOOK_BASE_URL: 'http://account-service-webhook:3001/webhooks' + + account-service-worker: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.account + command: ['worker'] + volumes: !reset [] + depends_on: !override + - redis + - ipfs + environment: + PROVIDER_BASE_URL: 'http://account-service-webhook:3001/webhooks/account-service' + WEBHOOK_BASE_URL: 'http://account-service-webhook:3001/webhooks' + + account-service-webhook: + image: rust:1.80.0 + volumes: + - ./services/account/rust-webhook-server:/app + ports: + - '3001:3001' + command: sh -c "cd /app && cargo run" + healthcheck: + test: ['CMD', 'curl', '-f', 'http://127.0.0.1:3001/webhooks/health'] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - amplica-gateway + + gateway-base: + profiles: + - skip + content-publishing-service-worker: + profiles: + - skip + content-publishing-service-api: + profiles: + - skip + content-watcher-service: + profiles: + - skip + graph-service-api: + profiles: + - skip + graph-service-worker: + profiles: + - skip diff --git a/docker-compose-k6.content-publishing.yaml b/docker-compose-k6.content-publishing.yaml new file mode 100644 index 00000000..233eebd5 --- /dev/null +++ b/docker-compose-k6.content-publishing.yaml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json + +services: + content-publishing-service-api: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.content-publishing + command: ['api'] + ports: + - 3000:3000 + volumes: !reset [] + depends_on: !override + - redis + - ipfs + + content-publishing-service-worker: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.content-publishing + command: ['worker'] + volumes: !reset [] + depends_on: !override + - redis + - ipfs + + gateway-base: + profiles: + - skip + account-service-worker: + profiles: + - skip + account-service-api: + profiles: + - skip + content-watcher-service: + profiles: + - skip + graph-service-api: + profiles: + - skip + graph-service-worker: + profiles: + - skip diff --git a/docker-compose-k6.content-watcher.yaml b/docker-compose-k6.content-watcher.yaml new file mode 100644 index 00000000..aa4cf5de --- /dev/null +++ b/docker-compose-k6.content-watcher.yaml @@ -0,0 +1,36 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json + +services: + content-watcher-service: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.content-watcher + ports: + - 3000:3000 + volumes: !reset [] + depends_on: !override + - redis + - ipfs + + gateway-base: + profiles: + - skip + content-publishing-service-worker: + profiles: + - skip + content-publishing-service-api: + profiles: + - skip + account-service-worker: + profiles: + - skip + account-service-api: + profiles: + - skip + graph-service-api: + profiles: + - skip + graph-service-worker: + profiles: + - skip diff --git a/docker-compose-k6.graph.yaml b/docker-compose-k6.graph.yaml new file mode 100644 index 00000000..8fabf202 --- /dev/null +++ b/docker-compose-k6.graph.yaml @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json + +services: + graph-service-api: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.graph + command: ['api'] + ports: + - 3000:3000 + volumes: !reset [] + depends_on: !override + - redis + + graph-service-worker: + pull_policy: never + build: + context: . + dockerfile: ./Docker/Dockerfile.graph + command: ['worker'] + volumes: !reset [] + depends_on: !override + - redis + + gateway-base: + profiles: + - skip + content-publishing-service-worker: + profiles: + - skip + content-publishing-service-api: + profiles: + - skip + content-watcher-service: + profiles: + - skip + account-service-api: + profiles: + - skip + account-service-worker: + profiles: + - skip diff --git a/docker-compose.yaml b/docker-compose.yaml index 35205309..107e8fa4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json x-common-environment: &common-environment FREQUENCY_URL: ${FREQUENCY_URL:-ws://frequency:9944} FREQUENCY_HTTP_URL: ${FREQUENCY_HTTP_URL:-http://localhost:9944} @@ -55,7 +56,7 @@ services: - redis_data:/data/redis frequency: - image: dsnp/instant-seal-node-with-deployed-schemas:latest + image: frequencychain/standalone-node:v1.13.0-rc3 # We need to specify the platform because it's the only image # built by Frequency at the moment, and auto-pull won't work otherwise platform: linux/amd64 @@ -159,7 +160,6 @@ services: - graph_node_cache:/app/services/graph/node_modules depends_on: - redis - - ipfs - gateway-base networks: - amplica-gateway @@ -174,7 +174,6 @@ services: - graph_node_cache:/app/services/graph/node_modules depends_on: - redis - - ipfs - gateway-base networks: - amplica-gateway diff --git a/services/account/docs/account_service_arch.drawio b/services/account/docs/account_service_arch.drawio index 318fc2e9..9fc05541 100644 --- a/services/account/docs/account_service_arch.drawio +++ b/services/account/docs/account_service_arch.drawio @@ -10,7 +10,7 @@ - + diff --git a/services/account/k6-test/README.md b/services/account/k6-test/README.md index 72e4023f..c3792a97 100644 --- a/services/account/k6-test/README.md +++ b/services/account/k6-test/README.md @@ -1,7 +1,7 @@ # Generated k6 script -The `account-service-load.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. -The `health-check.js` file contains a simple health check script that can be used to check the health of the service. +The `account-service-load.k6.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. +The `health-check.k6.js` file contains a simple health check script that can be used to check the health of the service. Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. @@ -22,5 +22,11 @@ To run the script, you need to have k6 installed. You can download it from [here To run the script, execute the following command: ```bash -k6 run health-check.js +k6 run health-check.k6.js ``` + +## Generating Test Data + +So that k6 doesn't have to interact with the chain, we can generate data sepatately and then use it for the tests. + +`npm run generate:signup` for example will re-generate the `signups.gen.js` file with 100 generated valid signup payloads. diff --git a/services/account/k6-test/account-service-load.js b/services/account/k6-test/account-service-load.k6.skip.js similarity index 74% rename from services/account/k6-test/account-service-load.js rename to services/account/k6-test/account-service-load.k6.skip.js index a719f742..c484afcb 100644 --- a/services/account/k6-test/account-service-load.js +++ b/services/account/k6-test/account-service-load.k6.skip.js @@ -36,63 +36,7 @@ const SLEEP_DURATION = 0.1; // Global variables should be initialized. export default function () { - group('health', () => { - // Request No. 1: ApiController_health - // eslint-disable-next-line no-lone-blocks - { - const url = `${BASE_URL}/healthz`; - const request = http.get(url); - - check(request, { - 'Service is healthy': (r) => r.status === 200, - }); - } - }); - - group('/v1/accounts/siwf', () => { - // Request No. 1: AccountsController_getSIWFConfig - { - const url = `${BASE_URL}/v1/accounts/siwf`; - const request = http.get(url); - - check(request, { - 'Returned SIWF Configuration data': (r) => r.status === 200, - }); - - sleep(SLEEP_DURATION); - } - - // Request No. 2: AccountsController_postSignInWithFrequency - { - const url = `${BASE_URL}/v1/accounts/siwf`; - // Use the SIWF sample Sign Up request body for a new user. - const body = { - signUp: { - extrinsics: [ - { - pallet: 'msa', - extrinsicName: 'createSponsoredAccountWithDelegation', - encodedExtrinsic: - '0xed01043c01b01b4dcafc8a8e73bff98e7558249f53cd0e0e64fa6b8f0159f0913d4874d9360176644186458bad3b00bbd0ac21e6c9bd5a8bed9ced7a772d11a9aac025b47f6559468808e272696f596a02af230951861027c0dc30f7163ecf316838a0723483010000000000000014000000000000000000004d000000', - }, - { - pallet: 'handles', - extrinsicName: 'claimHandle', - encodedExtrinsic: - '0xb901044200b01b4dcafc8a8e73bff98e7558249f53cd0e0e64fa6b8f0159f0913d4874d93601225508ae2da9804c60660a150277eb32b2a0f6b9c8f6e07dd6cad799cb31ae1dfb43896f488e9c0b7ec8b530d930b3f9b690683f2765d5def3fee3fc6540d58714656e6464794d000000', - }, - ], - }, - }; - const params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; - const request = http.post(url, JSON.stringify(body), params); - - check(request, { - 'Signed in successfully': (r) => r.status === 201, - }); - } - }); - + // Needs 255 generated accounts group('/v1/accounts/{msaId}', () => { const msaId = randomIntBetween(2, 256); @@ -107,6 +51,7 @@ export default function () { } }); + // Needs 255 generated accounts group('/v1/delegation/{msaId}', () => { const msaId = randomIntBetween(2, 256); @@ -121,6 +66,7 @@ export default function () { } }); + // Needs generation group('/v1/keys/add', () => { // Request No. 1: KeysController_addKey // eslint-disable-next-line no-lone-blocks @@ -152,6 +98,7 @@ export default function () { }); }); + // Needs 255 generated accounts with KEYS group('/v1/keys/{msaId}', () => { const msaId = randomIntBetween(2, 256); @@ -166,6 +113,7 @@ export default function () { } }); + // Needs generation group('/v1/handles', () => { // Request No. 1: HandlesController_createHandle // eslint-disable-next-line no-lone-blocks @@ -196,6 +144,7 @@ export default function () { } }); + // Needs generation group('/v1/handles/change', () => { // Request No. 1: HandlesController_changeHandle // eslint-disable-next-line no-lone-blocks @@ -226,6 +175,7 @@ export default function () { } }); + // Needs 1 generated accounts group('/v1/handles/{msaId}', () => { const msaId = '2'; diff --git a/services/account/k6-test/generate/helpers/chain.mjs b/services/account/k6-test/generate/helpers/chain.mjs new file mode 100644 index 00000000..39931215 --- /dev/null +++ b/services/account/k6-test/generate/helpers/chain.mjs @@ -0,0 +1,26 @@ +// Only way to silence PolkadotJS API warnings we don't want +console.warn = () => {}; + +import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; +import { mnemonicGenerate } from '@polkadot/util-crypto'; +import { u8aToHex, u8aWrapBytes } from '@polkadot/util'; + +export async function getApi() { + const api = await ApiPromise.create({ + // Using mainnet as: 1. Nothing here would use local only metadata, 2. We aren't actually submitting. + provider: new WsProvider('wss://0.rpc.frequency.xyz'), + }); + await api.isReady; + return api; +} + +export function createKey() { + const mnemonic = mnemonicGenerate(); + const keyring = new Keyring({ type: 'sr25519' }); + const keypair = keyring.createFromUri(mnemonic); + return keypair; +} + +export function signPayloadSr25519(keys, data) { + return { Sr25519: u8aToHex(keys.sign(u8aWrapBytes(data.toU8a()))) }; +} diff --git a/services/account/k6-test/generate/signups.mjs b/services/account/k6-test/generate/signups.mjs new file mode 100644 index 00000000..d02ecdea --- /dev/null +++ b/services/account/k6-test/generate/signups.mjs @@ -0,0 +1,69 @@ +import fs from 'fs/promises'; +import { Bytes } from '@polkadot/types'; +import { getApi, createKey, signPayloadSr25519 } from './helpers/chain.mjs'; + +const GENERATE_COUNT = 10; + +const signup = (api) => { + const keypair = createKey(); + const expiration = 100; // Will this fail when running against longer chains? + + const addProviderData = api.registry.createType('PalletMsaAddProvider', { + authorizedMsaId: 1, + schemaIds: [1], + expiration, + }); + const createTx = api.tx.msa.createSponsoredAccountWithDelegation( + keypair.address, + signPayloadSr25519(keypair, addProviderData), + addProviderData, + ); + const claimHandlePayload = api.registry.createType('CommonPrimitivesHandlesClaimHandlePayload', { + baseHandle: new Bytes(api.registry, keypair.address.substring(0, 8)), + expiration, + }); + const claimTx = api.tx.handles.claimHandle( + keypair.address, + signPayloadSr25519(keypair, claimHandlePayload), + claimHandlePayload, + ); + + return { + signUp: { + extrinsics: [ + { + pallet: 'msa', + extrinsicName: 'createSponsoredAccountWithDelegation', + encodedExtrinsic: createTx.toHex(), + }, + { + pallet: 'handles', + extrinsicName: 'claimHandle', + encodedExtrinsic: claimTx.toHex(), + }, + ], + }, + }; +}; + +const main = async () => { + const api = await getApi(); + const generated = []; + for (let i = 0; i < GENERATE_COUNT; i++) { + generated.push(signup(api)); + } + // Write the generated array to './signups.gen.js' + const fileContent = ` + // GENERATED FILE! Regenerate using npm run generate:signups + export const signups = ${JSON.stringify(generated, null, 2)}; + `; + + try { + await fs.writeFile('./signups.gen.js', fileContent, 'utf8'); + console.log('Successfully wrote to ./signups.gen.js'); + } catch (err) { + console.error('Error writing to file:', err); + } +}; + +main().catch(console.error).finally(process.exit); diff --git a/services/account/k6-test/health-check.js b/services/account/k6-test/health-check.k6.js similarity index 95% rename from services/account/k6-test/health-check.js rename to services/account/k6-test/health-check.k6.js index cacce951..c7eb063b 100644 --- a/services/account/k6-test/health-check.js +++ b/services/account/k6-test/health-check.k6.js @@ -31,7 +31,7 @@ const SLEEP_DURATION = 0.1; export default function () { // Request No. 1: ApiController_health // eslint-disable-next-line no-lone-blocks - const url = `${BASE_URL}/api/health`; + const url = `${BASE_URL}/healthz`; const request = http.get(url); check(request, { diff --git a/services/account/k6-test/new-sign-up.js b/services/account/k6-test/new-sign-up.k6.js similarity index 65% rename from services/account/k6-test/new-sign-up.js rename to services/account/k6-test/new-sign-up.k6.js index b5414b48..20c2e575 100644 --- a/services/account/k6-test/new-sign-up.js +++ b/services/account/k6-test/new-sign-up.k6.js @@ -2,7 +2,8 @@ /* eslint-disable func-names */ /* * Account Service - * Account Service API + * Account Service API: New Sign Ups + * NOTE: This test MUST run on a clean chain and cannot be re-run without running: npm run generate:signups * * OpenAPI spec version: 1.0 * @@ -14,6 +15,8 @@ import http from 'k6/http'; import { group, check, sleep } from 'k6'; +import exec from 'k6/execution'; +import { signups } from './signups.gen.js'; export const options = { // scenarios: { @@ -43,38 +46,39 @@ const CALLBACK_URL = 'http://localhost:3001/webhooks/account-service'; const BASE_URL = 'http://localhost:3000'; // Sleep duration between successive requests. const SLEEP_DURATION = 0.1; -const BLOCKTIME_SECONDS = 12; +const BLOCKTIME_SECONDS = 13; // Add 1 second for additional loop buffer // Global variables should be initialized. -function checkCallback() { +function checkCallback(referenceId) { const res = http.get(CALLBACK_URL); console.log('Callback response:', res.status, res.body); check(res, { 'callback received': (r) => r.status === 201, 'callback contains expected data': (r) => { const json = JSON.parse(r.body); - return json.referenceId === 'DekKx1F_WYOWCQFXE3vgwqXP4U4'; + return json.referenceId === referenceId; }, }); } -export function setup() { +export async function setup() { // Let's make sure the service is healthy before starting the test. console.log('Checking service health...'); - const res = http.get(`${BASE_URL}/api/health`); + const res = http.get(`${BASE_URL}/healthz`); console.log('Service health check status:', res.status); if (res.status !== 200) { console.error('Service is not healthy! Terminating test...'); return false; } - return true; + + return { signUpBody: signups[exec.vu.iterationInInstance] }; } -export default function () { - group('/accounts/siwf', () => { +export default function (setupData) { + group('/v1/accounts/siwf', () => { // Request No. 1: AccountsController_getSIWFConfig { - const url = `${BASE_URL}/accounts/siwf`; + const url = `${BASE_URL}/v1/accounts/siwf`; const request = http.get(url); check(request, { @@ -85,39 +89,25 @@ export default function () { } // Request No. 2: AccountsController_postSignInWithFrequency + let referenceId; { - const url = `${BASE_URL}/accounts/siwf`; + const url = `${BASE_URL}/v1/accounts/siwf`; // Use the SIWF sample Sign Up request body for a new user. - const body = { - signUp: { - extrinsics: [ - { - pallet: 'msa', - extrinsicName: 'createSponsoredAccountWithDelegation', - encodedExtrinsic: - '0xed01043c01b01b4dcafc8a8e73bff98e7558249f53cd0e0e64fa6b8f0159f0913d4874d9360176644186458bad3b00bbd0ac21e6c9bd5a8bed9ced7a772d11a9aac025b47f6559468808e272696f596a02af230951861027c0dc30f7163ecf316838a0723483010000000000000014000000000000000000004d000000', - }, - { - pallet: 'handles', - extrinsicName: 'claimHandle', - encodedExtrinsic: - '0xb901044200b01b4dcafc8a8e73bff98e7558249f53cd0e0e64fa6b8f0159f0913d4874d93601225508ae2da9804c60660a150277eb32b2a0f6b9c8f6e07dd6cad799cb31ae1dfb43896f488e9c0b7ec8b530d930b3f9b690683f2765d5def3fee3fc6540d58714656e6464794d000000', - }, - ], - }, - }; + const body = setupData.signUpBody; const params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; const request = http.post(url, JSON.stringify(body), params); check(request, { 'Signed in successfully': (r) => r.status === 201, + 'Has referenceId': (r) => !!JSON.parse(r.body).referenceId, }); + referenceId = JSON.parse(request.body).referenceId; } // The front end will poll the server for the account status. // We'll wait here for a block to be finalized. sleep(BLOCKTIME_SECONDS); - console.log('Block finalized. Checking callback...'); - checkCallback(); + console.log('Block finalized. Checking callback...', { referenceId }); + checkCallback(referenceId); }); } diff --git a/services/account/k6-test/package-lock.json b/services/account/k6-test/package-lock.json index c41fefa7..b8f75166 100644 --- a/services/account/k6-test/package-lock.json +++ b/services/account/k6-test/package-lock.json @@ -7,16 +7,857 @@ "": { "name": "k6-scripts", "version": "1.0.0", - "license": "ISC", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "^12.3.1", + "@polkadot/types": "^12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2" + }, "devDependencies": { "@types/k6": "^0.51.0" } }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz", + "integrity": "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA==", + "optional": true + }, + "node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1.tgz", + "integrity": "sha512-gmVDUP8LpCH0BXewbzqXF2sdHddq1H1q+XrAW2of+KZj4woQkIGBRGTJHeBEVHe30EB+UejR1N2dT4PO/RvDdg==", + "optional": true + }, + "node_modules/@polkadot-api/metadata-builders": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1.tgz", + "integrity": "sha512-GCI78BHDzXAF/L2pZD6Aod/yl82adqQ7ftNmKg51ixRL02JpWUA+SpUKTJE5MY1p8kiJJIo09P2um24SiJHxNA==", + "optional": true, + "dependencies": { + "@polkadot-api/substrate-bindings": "0.0.1", + "@polkadot-api/utils": "0.0.1" + } + }, + "node_modules/@polkadot-api/observable-client": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.1.0.tgz", + "integrity": "sha512-GBCGDRztKorTLna/unjl/9SWZcRmvV58o9jwU2Y038VuPXZcr01jcw/1O3x+yeAuwyGzbucI/mLTDa1QoEml3A==", + "optional": true, + "dependencies": { + "@polkadot-api/metadata-builders": "0.0.1", + "@polkadot-api/substrate-bindings": "0.0.1", + "@polkadot-api/substrate-client": "0.0.1", + "@polkadot-api/utils": "0.0.1" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@polkadot-api/substrate-bindings": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1.tgz", + "integrity": "sha512-bAe7a5bOPnuFVmpv7y4BBMRpNTnMmE0jtTqRUw/+D8ZlEHNVEJQGr4wu3QQCl7k1GnSV1wfv3mzIbYjErEBocg==", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.3.1", + "@polkadot-api/utils": "0.0.1", + "@scure/base": "^1.1.1", + "scale-ts": "^1.6.0" + } + }, + "node_modules/@polkadot-api/substrate-client": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.0.1.tgz", + "integrity": "sha512-9Bg9SGc3AwE+wXONQoW8GC00N3v6lCZLW74HQzqB6ROdcm5VAHM4CB/xRzWSUF9CXL78ugiwtHx3wBcpx4H4Wg==", + "optional": true + }, + "node_modules/@polkadot-api/utils": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.0.1.tgz", + "integrity": "sha512-3j+pRmlF9SgiYDabSdZsBSsN5XHbpXOAce1lWj56IEEaFZVjsiCaxDOA7C9nCcgfVXuvnbxqqEGQvnY+QfBAUw==", + "optional": true + }, + "node_modules/@polkadot/api": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-12.3.1.tgz", + "integrity": "sha512-VCrtadJRJttya5NhZ8slkD/UQyOZv4qABjagQMaG1eTZpn5k1wskmDUGdHrZZpYO5jBPewnCgaN8+LPKO2qiOw==", + "dependencies": { + "@polkadot/api-augment": "12.3.1", + "@polkadot/api-base": "12.3.1", + "@polkadot/api-derive": "12.3.1", + "@polkadot/keyring": "^13.0.2", + "@polkadot/rpc-augment": "12.3.1", + "@polkadot/rpc-core": "12.3.1", + "@polkadot/rpc-provider": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/types-augment": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/types-create": "12.3.1", + "@polkadot/types-known": "12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-augment": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-12.3.1.tgz", + "integrity": "sha512-KfofZVEUeTgLzcexdxKBT2vihazDheUoTLxbsa2ztmmw4UB/IjOL911y04pjg2obZQAI9B+oCyxJXyCfzauWEg==", + "dependencies": { + "@polkadot/api-base": "12.3.1", + "@polkadot/rpc-augment": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/types-augment": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-base": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-12.3.1.tgz", + "integrity": "sha512-vNbxXNjn4APfXg+ui99gurX2Jzns+eezmWranxoGXT7q0mme1zAtWus5t4e+qe1qRjDNZZYPruF7YJA8dL5k8A==", + "dependencies": { + "@polkadot/rpc-core": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/util": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-derive": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-12.3.1.tgz", + "integrity": "sha512-2MbK1h4GcKEdSgDKKYI28iZESw0VOm0kekV6YGQflZNWe84jJOn2rohP8pACseUjQjwWDgbPPBvTlRZTk7zdAw==", + "dependencies": { + "@polkadot/api": "12.3.1", + "@polkadot/api-augment": "12.3.1", + "@polkadot/api-base": "12.3.1", + "@polkadot/rpc-core": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/keyring": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-13.0.2.tgz", + "integrity": "sha512-NeLbhyKDT5W8LI9seWTZGePxNTOVpDhv2018HSrEDwJq9Ie0C4TZhUf3KNERCkSveuThXjfQJMs+1CF33ZXPWw==", + "dependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/util-crypto": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/util-crypto": "13.0.2" + } + }, + "node_modules/@polkadot/networks": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-13.0.2.tgz", + "integrity": "sha512-ABAL+vug/gIwkdFEzeh87JoJd0YKrxSYg/HjUrZ+Zis2ucxQEKpvtCpJ34ku+YrjacBfVqIAkkwd3ZdIPGq9aQ==", + "dependencies": { + "@polkadot/util": "13.0.2", + "@substrate/ss58-registry": "^1.46.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-augment": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-12.3.1.tgz", + "integrity": "sha512-/tZLl5IuQ4vdGlUAbd8x3ShZ35XDSeyknKHCC+9kIrM/+KIyoCYBob2RXP9uqX8m516AWkXUrjsSO6DFPBpRGg==", + "dependencies": { + "@polkadot/rpc-core": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-12.3.1.tgz", + "integrity": "sha512-bNo26P20nRpLfANTK4sWEakxvqBJpKwAp/Gt7KlxoGgoTUbWFapyGKScFxp/pblycEziEbC+ZjkLMkaWaqi69g==", + "dependencies": { + "@polkadot/rpc-augment": "12.3.1", + "@polkadot/rpc-provider": "12.3.1", + "@polkadot/types": "12.3.1", + "@polkadot/util": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-12.3.1.tgz", + "integrity": "sha512-Tg1Oj/1ldivqwnnOWepcNHEHYgpOBffxlrZMEXH1XX6D3AZaUhAWbatizyisydpuMbknTQ9FGYSnb9rOc2QBJw==", + "dependencies": { + "@polkadot/keyring": "^13.0.2", + "@polkadot/types": "12.3.1", + "@polkadot/types-support": "12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "@polkadot/x-fetch": "^13.0.2", + "@polkadot/x-global": "^13.0.2", + "@polkadot/x-ws": "^13.0.2", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.10" + } + }, + "node_modules/@polkadot/types": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-12.3.1.tgz", + "integrity": "sha512-4MkTF1znpgp9mZc/ZZPdFe7/5it9v+EJmzXc/DEOX9kVWs2BuKOWopzOFyO3reVUUB+v7dxfSOArSsxkMUcuoA==", + "dependencies": { + "@polkadot/keyring": "^13.0.2", + "@polkadot/types-augment": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/types-create": "12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-augment": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-12.3.1.tgz", + "integrity": "sha512-I3ggJt7W3UOScP6WA6PNmNsmpCfZtXkRJvSJkX7bi2LsSm/iF0xo0KdpQK02dHu7nGRFD9O5cSoVawzZJifGLA==", + "dependencies": { + "@polkadot/types": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-codec": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-12.3.1.tgz", + "integrity": "sha512-yZ4exsQI+eVkE/fZNuJBOajAgOH/YncKWOOf0N4lc6iq28oYp22DCAXc50Ym372l4HO+uhC9QdMPH9EiWwT2pQ==", + "dependencies": { + "@polkadot/util": "^13.0.2", + "@polkadot/x-bigint": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-create": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-12.3.1.tgz", + "integrity": "sha512-Jf9BByWB64FPW3lM5/Mcc/foyPJ3L9t0QwHVHaYWaonZt6l7SPX71rQmD7twJiTj9q1d1WidDKg/TtRDNbm1yA==", + "dependencies": { + "@polkadot/types-codec": "12.3.1", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-known": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-12.3.1.tgz", + "integrity": "sha512-G8t0uiIW1iu3KwylHDPnqdHxg5qwBxzPZQJvsjnGx2qBUk2VqXditKWcNFLEwCTnJPL95t2AzEO711lS99WRbg==", + "dependencies": { + "@polkadot/networks": "^13.0.2", + "@polkadot/types": "12.3.1", + "@polkadot/types-codec": "12.3.1", + "@polkadot/types-create": "12.3.1", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-support": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-12.3.1.tgz", + "integrity": "sha512-TwL5M5HkZ4jQGKekD+qJFLba7UXWASfwlPy2OpKj0LOnnmq4tudXgN13UFdQ7HoOmisPhr+vYo9vteYeBZ0jTA==", + "dependencies": { + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-13.0.2.tgz", + "integrity": "sha512-/6bS9sfhJLhs8QuqWaR1eRapzfDdGC5XAQZEPL9NN5sTTA7HxWos8rVleai0UERm8QUMabjZ9rK9KpzbXl7ojg==", + "dependencies": { + "@polkadot/x-bigint": "13.0.2", + "@polkadot/x-global": "13.0.2", + "@polkadot/x-textdecoder": "13.0.2", + "@polkadot/x-textencoder": "13.0.2", + "@types/bn.js": "^5.1.5", + "bn.js": "^5.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util-crypto": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.0.2.tgz", + "integrity": "sha512-woUsJJ6zd/caL7U+D30a5oM/+WK9iNI00Y8aNUHSj6Zq/KPzK9uqDBaLGWwlgrejoMQkxxiU2X0f2LzP15AtQg==", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@polkadot/networks": "13.0.2", + "@polkadot/util": "13.0.2", + "@polkadot/wasm-crypto": "^7.3.2", + "@polkadot/wasm-util": "^7.3.2", + "@polkadot/x-bigint": "13.0.2", + "@polkadot/x-randomvalues": "13.0.2", + "@scure/base": "^1.1.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2" + } + }, + "node_modules/@polkadot/wasm-bridge": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz", + "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==", + "dependencies": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz", + "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==", + "dependencies": { + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-init": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz", + "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz", + "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==", + "dependencies": { + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz", + "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==", + "dependencies": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-util": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz", + "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-bigint": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-13.0.2.tgz", + "integrity": "sha512-h2jKT/UaxiEal8LhQeH6+GCjO7GwEqVAD2SNYteCOXff6yNttqAZYJuHZsndbVjVNwqRNf8D5q/zZkD0HUd6xQ==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-fetch": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-13.0.2.tgz", + "integrity": "sha512-B/gf9iriUr6za/Ui7zIFBfHz7UBZ68rJEIteWHx1UHRCZPcLqv+hgpev6xIGrkfFljI0/lI7IwtN2qy6HYzFBg==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-global": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-13.0.2.tgz", + "integrity": "sha512-OoNIXLB5y8vIKpk4R+XmpDPhipNXWSUvEwUnpQT7NAxNLmzgMq1FhbrwBWWPRNHPrQonp7mqxV/X+v5lv1HW/g==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.0.2.tgz", + "integrity": "sha512-SGj+L0H/7TWZtSmtkWlixO4DFzXDdluI0UscN2h285os2Ns8PnmBbue+iJ8PVSzpY1BOxd66gvkkpboPz+jXFQ==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.0.2.tgz", + "integrity": "sha512-mauglOkTJxLGmLwLc3J5Jlq/W+SHP53eiy3F8/8JxxfnXrZKgWoQXGpvXYPjFnMZj0MzDSy/6GjyGWnDCgdQFA==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-textencoder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.0.2.tgz", + "integrity": "sha512-Lq08H2OnVXj97uaOwg7tcmRS7a4VJYkHEeWO4FyEMOk6P6lU6W8OVNjjxG0se9PCEgmyZPUDbJI//1ynzP4cXw==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-ws": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-13.0.2.tgz", + "integrity": "sha512-nC5e2eY5D5ZR5teQOB7ib+dWLbmNws86cTz3BjKCalSMBBIn6i3V9ElgABpierBmnSJe9D94EyrH1BxdVfDxUg==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scure/base": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", + "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@substrate/connect": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.8.10.tgz", + "integrity": "sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w==", + "deprecated": "versions below 1.x are no longer maintained", + "optional": true, + "dependencies": { + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.4", + "@substrate/light-client-extension-helpers": "^0.0.6", + "smoldot": "2.0.22" + } + }, + "node_modules/@substrate/connect-extension-protocol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.0.0.tgz", + "integrity": "sha512-nKu8pDrE3LNCEgJjZe1iGXzaD6OSIDD4Xzz/yo4KO9mQ6LBvf49BVrt4qxBFGL6++NneLiWUZGoh+VSd4PyVIg==", + "optional": true + }, + "node_modules/@substrate/connect-known-chains": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.2.2.tgz", + "integrity": "sha512-gOGrXSWA2d/3kf8Yco00VlHZl48smzAGW5Z9MDxMht98hRpT2yEEN4N5QdoEKMI4ihDW8goXGzmp79D0hFPpuA==", + "optional": true + }, + "node_modules/@substrate/light-client-extension-helpers": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.6.tgz", + "integrity": "sha512-girltEuxQ1BvkJWmc8JJlk4ZxnlGXc/wkLcNguhY+UoDEMBK0LsdtfzQKIfrIehi4QdeSBlFEFBoI4RqPmsZzA==", + "optional": true, + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.0.1", + "@polkadot-api/json-rpc-provider-proxy": "0.0.1", + "@polkadot-api/observable-client": "0.1.0", + "@polkadot-api/substrate-client": "0.0.1", + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.4", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "smoldot": "2.x" + } + }, + "node_modules/@substrate/ss58-registry": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.49.0.tgz", + "integrity": "sha512-leW6Ix4LD7XgvxT7+aobPWSw+WvPcN2Rxof1rmd0mNC5t2n99k1N7UNEvz7YEFSOUeHWmKIY7F5q8KeIqYoHfA==" + }, + "node_modules/@types/bn.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/k6": { "version": "0.51.0", "resolved": "https://registry.npmjs.org/@types/k6/-/k6-0.51.0.tgz", "integrity": "sha512-xelcvFGPI4VYrV5ozADmRuFQBKmDqDRzxfHVuCDD1/firZiSQvTP0pntxHuYUSkRyL8I83kvABXUlnLYNT2VuA==", "dev": true + }, + "node_modules/@types/node": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scale-ts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/scale-ts/-/scale-ts-1.6.0.tgz", + "integrity": "sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q==", + "optional": true + }, + "node_modules/smoldot": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.22.tgz", + "integrity": "sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g==", + "optional": true, + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/undici-types": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/services/account/k6-test/package.json b/services/account/k6-test/package.json index cc6985a3..aeb9ac01 100644 --- a/services/account/k6-test/package.json +++ b/services/account/k6-test/package.json @@ -4,12 +4,19 @@ "description": "The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs.", "main": "account-service-load.js", "scripts": { + "generate:signups": "node generate/signups.mjs", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", - "license": "ISC", + "license": "Apache-2.0", "devDependencies": { "@types/k6": "^0.51.0" + }, + "dependencies": { + "@polkadot/api": "^12.3.1", + "@polkadot/types": "^12.3.1", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2" } } diff --git a/services/account/k6-test/signups.gen.js b/services/account/k6-test/signups.gen.js new file mode 100644 index 00000000..04b3511d --- /dev/null +++ b/services/account/k6-test/signups.gen.js @@ -0,0 +1,165 @@ + + // GENERATED FILE! Regenerate using npm run generate:signups + export const signups = [ + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c0112314c23e4db5a72860c155340c795790090388f101e67d7b9770cc8b556ec6c017adc6b9662b45039b188b880e581a9c2e8e8416622ce2933d5495497c4e4a25d5c708380563daee96c079a764a4e79578b95419fa8147890693e46d40f34768f010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc50104420012314c23e4db5a72860c155340c795790090388f101e67d7b9770cc8b556ec6c0188346a1fdba5e63a91ce2de8e2e45d16c4af6476e2bed5469fc03565fbe0a973ae6257db65772322c4aeaa0494a91dcc1010d801452df284d03b3c2ad7fd168f203543555a4e327a6764000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c01788dce66a1b7bbd4e223f5a1ba10d93fdaaddefc151f6b221dd5e0ea564f2a7401963bb2e86a9ced2d2bee262027e7e177aab080ca5a597caab07f052c74e79353a79cf6914462096e98135bd5b3f46143bdbccc54dcd3eb1048a497b10f7f2286010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc501044200788dce66a1b7bbd4e223f5a1ba10d93fdaaddefc151f6b221dd5e0ea564f2a74013465e0e8814ecb2269a200a1e479826d6a9aba644b21d7f4c1a5eea3b80eb12214a5bccd2bf8ff92f356141eface7b9a41eac1b56afacdeb3d6cce596ecdc98d2035456e6d6a44436264000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c014e84760e864f18fa4aa8089f8cfa3347ce8533ccd898c386730d9bded366026201306663ea0dd22fff1df12db8a3dd19ecf94fede4568fe497f86d0a29471d3970cc45a11c3e8f1298b76ee3901ee1be5c01005beb2d880323be79f1652a279786010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc5010442004e84760e864f18fa4aa8089f8cfa3347ce8533ccd898c386730d9bded3660262012e4c32568af35395bdbfa19eade46fac0b7f26aa7ac2dabc1c589d0cc6e0b954e24c29611e17e112197e70a9b6f92ba2daf1ce1ce2056e538c7fbe84d8d5458420354471657759785664000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c010e554132a77274bde2071d06fe76108a01cfe701507b03c5f838595669cb217401b82c0f221789f8e194e1a712be81dec0f1b6d960a59286620b28bb6ecc531f2fd055247d4c3bf417a61c171f35abe126bb8263f7012b3485991bc898fec1548a010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc5010442000e554132a77274bde2071d06fe76108a01cfe701507b03c5f838595669cb21740156dd50096330a95b31f4c78aa698c5d85bcdb8a0df829291cfee59780e53cc5ebdf7c80d53e3abfc9ef8d949034719f657b4e508bcef5f96bad3fd3f8100e886203543505672544d3464000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c01205029209aded136c5b24887ea1ad4565ee60e438db6f5dcaca404d9fcc9085b012498e3447196cb2f93a8bd9e591b2733d8e48dfe6b54c89b5c87b17a21e73712714f734316d8e1668e580892a72de8713ae2e8dcecb5611ccf8215911dc52f8d010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc501044200205029209aded136c5b24887ea1ad4565ee60e438db6f5dcaca404d9fcc9085b01acc9b16de4f13cae4d0c249fb0c57f758923b26e25bba390d51585080c31506ce8b331421804bf93f051e5857b2cc459c01b898582336b68827c58f4fa4a0f802035436f3543674b4b64000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c014a7e9342f32196f3c7aa36154b0db60ad9047c697504edb5dd1abc1d947a3523011cd40bd17ee79bd8052927e17dabba10f691f21fc5da864ed36bf05d15fcdb4ae6ba78e770d63e05b19acd9d5f96a98a4f6a031887b1fd4a7cc5069c2aee918b010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc5010442004a7e9342f32196f3c7aa36154b0db60ad9047c697504edb5dd1abc1d947a352301b2eddcbc1e76154c75cea0799a5980ce73e2a525a71e7f23ab1b2eae31db8728b1c3c4060de5b7a02665de7bb54a719c1c63c2e35edc09ce7294659ef40e1c832035446b503132697664000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c01d6c88bf28f9299ff371871695eebf3602678ff3e8060b8668cae634709bf0902014c8b7c372a92560116a3b477938ca5ccbd9667f82de9144cfbc95de0d2ae06687c1e145acb401e3e8ae151fe6e33b2d66724cca9c7db2ec6fa525f26fef89d82010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc501044200d6c88bf28f9299ff371871695eebf3602678ff3e8060b8668cae634709bf09020172686f0a2ecb34fbb019c05fe4f32a69c08d584208fc8d6e695836116a9c8005f89a664f22aee2658428fe1ac4d67183142eada0bfdbd77958cb4dd58488d387203547764b674d506764000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c01f8b4e06f5c5ae437e8485ae9ec19edb3036f2f94c9a88e67a4c0b747a0512f720170886a75b4032f33e95d1ef9641742f65127d6f8863a9bb5bea007a54d65572e148731654954fd764cc7fa770d0c8f5d8fc73b1393afe9f12ac7efa2b3efce87010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc501044200f8b4e06f5c5ae437e8485ae9ec19edb3036f2f94c9a88e67a4c0b747a0512f7201feb5612ba688d4bb10eadd84b6b1be075376e1cdb812019715ec9c92bf4bb8339dda3899627c17149a9039e8c67cc8cbaa0c1f8a7d1f85eb4e172606a274968d203548676f5473697064000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c012855dc9998ad16b1eab804424d214da7acf87c14b8b27ed33bb2f838cb06547801aa9d429155da56378e2902b40a2ad7dcb5f3002102284547ef3e614eddf5c87215ccc29c7276ba32811337bef430829f46e15d3b6fc32c18133c9cc19c69ec89010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc5010442002855dc9998ad16b1eab804424d214da7acf87c14b8b27ed33bb2f838cb065478017c2675d392d7ba33813a51560811674ac42966ac21032527fa5a350cb1674b791d2cbd429664ce7cf8a18923264dab05aa42223e1398d33631cdc3a81e2d008a20354379624838414364000000" + } + ] + } + }, + { + "signUp": { + "extrinsics": [ + { + "pallet": "msa", + "extrinsicName": "createSponsoredAccountWithDelegation", + "encodedExtrinsic": "0xcd01043c016044d8001ffea105dab1583161003d65e2bd9c11d5a1cb777efa1df6877080340112c71f897faf2dd9f11b71d2f49563aba27d9f774907d02a5d1112097b110233291c0e86c454e2add1004f7c88c149ede1786154bbffc3ce4b80b2efac8cfe80010000000000000004010064000000" + }, + { + "pallet": "handles", + "extrinsicName": "claimHandle", + "encodedExtrinsic": "0xc5010442006044d8001ffea105dab1583161003d65e2bd9c11d5a1cb777efa1df687708034014ccc9ea7af7132083482f69262818e8cd7224d475932f14188b69afbad9f8e2f67649c8e4cbe9090d6ac9fda8cd934151735a5cd5dc4c460c4e58f475c00da8020354545767553587164000000" + } + ] + } + } +]; + \ No newline at end of file diff --git a/services/account/package.json b/services/account/package.json index 8259247a..0ab27268 100644 --- a/services/account/package.json +++ b/services/account/package.json @@ -27,8 +27,8 @@ "test": "jest --coverage --verbose", "test:e2e": "set -a ; . .env ; jest --verbose --silent --runInBand --testRegex \".e2e-spec.ts\" --detectOpenHandles --forceExit", "test:e2e:verbose": "set -a ; . .env ; jest --verbose --runInBand --testRegex \".e2e-spec.ts\" --detectOpenHandles --forceExit", - "test:k6:account-service": "k6 run k6-test/account-service-load.js", - "test:k6:new-sign-up": "k6 run k6-test/new-sign-up.js", + "test:k6:account-service": "k6 run k6-test/account-service-load.k6.js", + "test:k6:new-sign-up": "k6 run k6-test/new-sign-up.k6.js", "test:k6:health-check": "k6 run k6-test/health-check.js", "test:k6": "npm run test:k6:account-service ; npm run test:k6:new-sign-up ; npm run test:k6:health-check" }, diff --git a/services/account/rust-webhook-server/src/main.rs b/services/account/rust-webhook-server/src/main.rs index 5605ef5c..b6f7eade 100644 --- a/services/account/rust-webhook-server/src/main.rs +++ b/services/account/rust-webhook-server/src/main.rs @@ -22,7 +22,7 @@ const WEBHOOK_ENDPOINT: &str = "/account-service"; #[actix_web::main] async fn main() -> Result<(), impl Error> { - const HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); + const HOST: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); const PORT: u16 = 3001; env_logger::init(); println!( diff --git a/services/content-publishing/k6-test/README.md b/services/content-publishing/k6-test/README.md index 64a75216..5afbf52b 100644 --- a/services/content-publishing/k6-test/README.md +++ b/services/content-publishing/k6-test/README.md @@ -1,6 +1,6 @@ # Generated k6 script -The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. +The `script.k6.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. diff --git a/services/content-publishing/k6-test/health-check.k6.js b/services/content-publishing/k6-test/health-check.k6.js new file mode 100644 index 00000000..c7eb063b --- /dev/null +++ b/services/content-publishing/k6-test/health-check.k6.js @@ -0,0 +1,41 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable func-names */ +/* + * Account Service + * Account Service API + * + * OpenAPI spec version: 1.0 + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://github.com/OpenAPITools/openapi-generator + * + * Generator version: 7.7.0-SNAPSHOT + */ + +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export const options = { + vus: 100, + duration: '10s', + thresholds: { + http_req_duration: ['avg<100', 'p(95)<200'], + }, + noConnectionReuse: true, +}; + +const BASE_URL = 'http://localhost:3000'; +// Sleep duration between successive requests. +const SLEEP_DURATION = 0.1; + +export default function () { + // Request No. 1: ApiController_health + // eslint-disable-next-line no-lone-blocks + const url = `${BASE_URL}/healthz`; + const request = http.get(url); + + check(request, { + 'Service is healthy': (r) => r.status === 200, + }); + sleep(SLEEP_DURATION); +} diff --git a/services/content-publishing/k6-test/script.js b/services/content-publishing/k6-test/script.k6.js similarity index 100% rename from services/content-publishing/k6-test/script.js rename to services/content-publishing/k6-test/script.k6.js diff --git a/services/content-publishing/package.json b/services/content-publishing/package.json index 48aec678..73f8b4fb 100644 --- a/services/content-publishing/package.json +++ b/services/content-publishing/package.json @@ -26,7 +26,7 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", "test": "jest --coverage --verbose", - "test:k6:script": "k6 run k6-test/script.js", + "test:k6:script": "k6 run k6-test/script.k6.js", "test:k6:script-sm": "k6 run k6-test/script_sm_files.js", "test:k6:script-md": "k6 run k6-test/script_md_files.js", "test:k6:script-lg": "k6 run k6-test/script_lg_files.js", diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 08b5c39e..5cba09fb 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -25,7 +25,7 @@ describe('Content Watcher E2E request verification!', () => { }); }, 15000); - it('(GET) /api/health', () => request(WATCHER_URI).get('/api/health').expect(200).expect({ status: 200 })); + it('(GET) /healthz', () => request(WATCHER_URI).get('/healthz').expect(200).expect({ status: 200 })); it('(Post) /api/resetScanner', async () => { const resetScannerDto: ResetScannerDto = { diff --git a/services/content-watcher/k6-test/README.md b/services/content-watcher/k6-test/README.md new file mode 100644 index 00000000..e312f96f --- /dev/null +++ b/services/content-watcher/k6-test/README.md @@ -0,0 +1,25 @@ +# Generated k6 script + +The `health-check.k6.js` file contains a simple health check script that can be used to check the health of the service. + +Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. + +If the Swagger/OpenAPI specification used as the input spec contains examples at parameter level, those will be extracted and utilized as parameter values. The `handleParamValue` custom Mustache lambda registered for use in the K6 `script.mustache` template handles the conditional checks, formatting, and outputting of parameter values. If a given parameter has value specified – either in `example` or `examples` field, defined at the parameter level – that value will be used. For list (`examples`), entire list will be output in the generated script and the first element from that list will be assigned as parameter value. If a given parameter does not have an example defined, a placeholder value with `TODO_EDIT_THE_` prefix will be generated for that parameter, and you will have to assign a value before you can run the script. In other words, you can now generate K6 test scripts which are ready to run, provided the Swagger/OpenAPI specification used as the input spec contains examples for all of the path/query parameters; see `modules/openapi-generator/src/test/resources/3_0/examples.yaml` for an example of such specification, and for more information about adding examples. + +k6 specific parameters are in the [`params`](https://docs.k6.io/docs/params-k6http) object, and `body` contains the [request](https://docs.k6.io/docs/http-requests) body which is in the form of `identifier: type`, which the `type` should be substituted by a proper value. Then goes the request and the check. + +[Check](https://docs.k6.io/docs/checks) are like asserts but differ in that they don't halt execution, instead they just store the result of the check, pass or fail, and let the script execution continue. + +Each request is always followed by a 0.1 second [sleep](https://docs.k6.io/docs/sleep-t-1) to prevent the script execution from flooding the system with too many requests simultaneously. + +Note that the default iteration count and VU count is 1. So each request in each group will be executed once. For more information, see the [k6 options](https://docs.k6.io/docs/options). + +## Running the script + +To run the script, you need to have k6 installed. You can download it from [here](https://k6.io/docs/getting-started/installation). + +To run the script, execute the following command: + +```bash +k6 run health-check.k6.js +``` diff --git a/services/content-watcher/k6-test/health-check.k6.js b/services/content-watcher/k6-test/health-check.k6.js new file mode 100644 index 00000000..c7eb063b --- /dev/null +++ b/services/content-watcher/k6-test/health-check.k6.js @@ -0,0 +1,41 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable func-names */ +/* + * Account Service + * Account Service API + * + * OpenAPI spec version: 1.0 + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://github.com/OpenAPITools/openapi-generator + * + * Generator version: 7.7.0-SNAPSHOT + */ + +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export const options = { + vus: 100, + duration: '10s', + thresholds: { + http_req_duration: ['avg<100', 'p(95)<200'], + }, + noConnectionReuse: true, +}; + +const BASE_URL = 'http://localhost:3000'; +// Sleep duration between successive requests. +const SLEEP_DURATION = 0.1; + +export default function () { + // Request No. 1: ApiController_health + // eslint-disable-next-line no-lone-blocks + const url = `${BASE_URL}/healthz`; + const request = http.get(url); + + check(request, { + 'Service is healthy': (r) => r.status === 200, + }); + sleep(SLEEP_DURATION); +} diff --git a/services/content-watcher/k6-test/package-lock.json b/services/content-watcher/k6-test/package-lock.json new file mode 100644 index 00000000..4d22478e --- /dev/null +++ b/services/content-watcher/k6-test/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "k6-scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "k6-scripts", + "version": "1.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@types/k6": "^0.51.0" + } + }, + "node_modules/@types/k6": { + "version": "0.51.0", + "resolved": "https://registry.npmjs.org/@types/k6/-/k6-0.51.0.tgz", + "integrity": "sha512-xelcvFGPI4VYrV5ozADmRuFQBKmDqDRzxfHVuCDD1/firZiSQvTP0pntxHuYUSkRyL8I83kvABXUlnLYNT2VuA==", + "dev": true + } + } +} diff --git a/services/content-watcher/k6-test/package.json b/services/content-watcher/k6-test/package.json new file mode 100644 index 00000000..c679dbc6 --- /dev/null +++ b/services/content-watcher/k6-test/package.json @@ -0,0 +1,15 @@ +{ + "name": "k6-scripts", + "version": "1.0.0", + "description": "The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs.", + "main": "health-check.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "Apache-2.0", + "devDependencies": { + "@types/k6": "^0.51.0" + } +} diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index ac8822d0..db9eea7b 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -18,7 +18,7 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest --coverage --verbose", "test:e2e": "set -a ; . ./.env; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", - "test:k6": "echo 'TODO: K6 tests not implemented for content-watcher'", + "test:k6": "k6 run k6-test/health-check.k6.js", "local:init": "node scripts/chain-setup/local-chain-setup.cjs", "local:publish": "cd scripts/content-setup && npm i && npm run main", "local:webhook": "node scripts/webhook-cat.cjs", diff --git a/services/graph/k6-test/README.md b/services/graph/k6-test/README.md index 64a75216..5afbf52b 100644 --- a/services/graph/k6-test/README.md +++ b/services/graph/k6-test/README.md @@ -1,6 +1,6 @@ # Generated k6 script -The `script.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. +The `script.k6.js` file contains most of the Swagger/OpenAPI specification and you can customize it to your needs. Global header variables are defined at the top of the file, like `api_key`. Each path in the specification is converted into a [group](https://docs.k6.io/docs/tags-and-groups) in k6 and each group contains all the request methods related to that path. Path and query parameters are extracted from the specification and put at the start of the group. The URL is constructed from the base URL plus path and query. diff --git a/services/graph/k6-test/health-check.k6.js b/services/graph/k6-test/health-check.k6.js new file mode 100644 index 00000000..c7eb063b --- /dev/null +++ b/services/graph/k6-test/health-check.k6.js @@ -0,0 +1,41 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable func-names */ +/* + * Account Service + * Account Service API + * + * OpenAPI spec version: 1.0 + * + * NOTE: This class is auto generated by OpenAPI Generator. + * https://github.com/OpenAPITools/openapi-generator + * + * Generator version: 7.7.0-SNAPSHOT + */ + +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export const options = { + vus: 100, + duration: '10s', + thresholds: { + http_req_duration: ['avg<100', 'p(95)<200'], + }, + noConnectionReuse: true, +}; + +const BASE_URL = 'http://localhost:3000'; +// Sleep duration between successive requests. +const SLEEP_DURATION = 0.1; + +export default function () { + // Request No. 1: ApiController_health + // eslint-disable-next-line no-lone-blocks + const url = `${BASE_URL}/healthz`; + const request = http.get(url); + + check(request, { + 'Service is healthy': (r) => r.status === 200, + }); + sleep(SLEEP_DURATION); +} diff --git a/services/graph/package.json b/services/graph/package.json index add42c7f..b1eb33ee 100644 --- a/services/graph/package.json +++ b/services/graph/package.json @@ -27,7 +27,7 @@ "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "test": "jest --coverage --verbose", "test:e2e": "set -a ; . .env ; jest --testRegex \".e2e-spec.ts$\" --detectOpenHandles", - "test:k6": "k6 run k6-test/script.js" + "test:k6": "k6 run k6-test/script.k6.js" }, "repository": { "type": "git", diff --git a/tools/ci-k6/main.mjs b/tools/ci-k6/main.mjs new file mode 100644 index 00000000..c43ba5d3 --- /dev/null +++ b/tools/ci-k6/main.mjs @@ -0,0 +1,52 @@ +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { Keyring } from '@polkadot/keyring'; + +const keyring = new Keyring({ type: 'sr25519' }); + +export async function createAndStake(providerUrl, keyUri) { + const api = await ApiPromise.create({ provider: new WsProvider(providerUrl) }); + + console.log('Connected...'); + + const account = keyring.createFromUri(keyUri); + + const call = api.tx.utility.batchAll([ + api.tx.msa.create(), + api.tx.msa.createProvider('alice'), + api.tx.capacity.stake(1, 10_000_000_000_000), + ]); + + console.log('Submitting call...'); + await new Promise(async (resolve, reject) => { + const unsub = await call.signAndSend(account, ({ status, events }) => { + if (status.isInBlock || status.isFinalized) { + console.log( + `Block hash: ${(status.isInBlock && status.asInBlock) || (status.isFinalized && status.asFinalized)}`, + ); + if (events) + console.log( + 'All Events', + events.map((x) => x.toHuman()), + ); + const success = events.find((x) => api.events.system.ExtrinsicSuccess.is(x.event)); + const failure = events.find((x) => api.events.system.ExtrinsicFailed.is(x.event)); + unsub(); + if (success && !failure) { + console.log('Success!'); + resolve(); + } else { + console.error('FAILED!'); + reject(); + } + } + }); + }); +} + +try { + await createAndStake('ws://localhost:9944', '//Alice'); + process.exit(0); +} catch (error) { + console.error('Error:', error); + process.exit(1); +} diff --git a/tools/ci-k6/package-lock.json b/tools/ci-k6/package-lock.json new file mode 100644 index 00000000..ff290439 --- /dev/null +++ b/tools/ci-k6/package-lock.json @@ -0,0 +1,852 @@ +{ + "name": "ci-k6", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ci-k6", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "^12.2.3", + "@polkadot/keyring": "^13.0.2" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz", + "integrity": "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA==", + "optional": true + }, + "node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1.tgz", + "integrity": "sha512-gmVDUP8LpCH0BXewbzqXF2sdHddq1H1q+XrAW2of+KZj4woQkIGBRGTJHeBEVHe30EB+UejR1N2dT4PO/RvDdg==", + "optional": true + }, + "node_modules/@polkadot-api/metadata-builders": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1.tgz", + "integrity": "sha512-GCI78BHDzXAF/L2pZD6Aod/yl82adqQ7ftNmKg51ixRL02JpWUA+SpUKTJE5MY1p8kiJJIo09P2um24SiJHxNA==", + "optional": true, + "dependencies": { + "@polkadot-api/substrate-bindings": "0.0.1", + "@polkadot-api/utils": "0.0.1" + } + }, + "node_modules/@polkadot-api/observable-client": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.1.0.tgz", + "integrity": "sha512-GBCGDRztKorTLna/unjl/9SWZcRmvV58o9jwU2Y038VuPXZcr01jcw/1O3x+yeAuwyGzbucI/mLTDa1QoEml3A==", + "optional": true, + "dependencies": { + "@polkadot-api/metadata-builders": "0.0.1", + "@polkadot-api/substrate-bindings": "0.0.1", + "@polkadot-api/substrate-client": "0.0.1", + "@polkadot-api/utils": "0.0.1" + }, + "peerDependencies": { + "rxjs": ">=7.8.0" + } + }, + "node_modules/@polkadot-api/substrate-bindings": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1.tgz", + "integrity": "sha512-bAe7a5bOPnuFVmpv7y4BBMRpNTnMmE0jtTqRUw/+D8ZlEHNVEJQGr4wu3QQCl7k1GnSV1wfv3mzIbYjErEBocg==", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.3.1", + "@polkadot-api/utils": "0.0.1", + "@scure/base": "^1.1.1", + "scale-ts": "^1.6.0" + } + }, + "node_modules/@polkadot-api/substrate-client": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.0.1.tgz", + "integrity": "sha512-9Bg9SGc3AwE+wXONQoW8GC00N3v6lCZLW74HQzqB6ROdcm5VAHM4CB/xRzWSUF9CXL78ugiwtHx3wBcpx4H4Wg==", + "optional": true + }, + "node_modules/@polkadot-api/utils": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.0.1.tgz", + "integrity": "sha512-3j+pRmlF9SgiYDabSdZsBSsN5XHbpXOAce1lWj56IEEaFZVjsiCaxDOA7C9nCcgfVXuvnbxqqEGQvnY+QfBAUw==", + "optional": true + }, + "node_modules/@polkadot/api": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-12.2.3.tgz", + "integrity": "sha512-qpC29Uq0JZh/7Spcvmw+jUREG/ZYeb7miGUKomqHqU1hwBvyk9bqy7Vr10g3Hh0bkl5nP29YmnrLrG0NG+EtPg==", + "dependencies": { + "@polkadot/api-augment": "12.2.3", + "@polkadot/api-base": "12.2.3", + "@polkadot/api-derive": "12.2.3", + "@polkadot/keyring": "^13.0.2", + "@polkadot/rpc-augment": "12.2.3", + "@polkadot/rpc-core": "12.2.3", + "@polkadot/rpc-provider": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/types-augment": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/types-create": "12.2.3", + "@polkadot/types-known": "12.2.3", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-augment": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-12.2.3.tgz", + "integrity": "sha512-w3FYQAzVzZuD1xAUGwEeEftJr5N5oYigItrWkEc3nk+I3wUjNuHNlab3hCJZslRlHrE2zYVK5mGDDZYVPyn86Q==", + "dependencies": { + "@polkadot/api-base": "12.2.3", + "@polkadot/rpc-augment": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/types-augment": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-base": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-12.2.3.tgz", + "integrity": "sha512-fUJt3+uvBViwjz5tiiEE1VQkcDiXLzAPdex2OeECXopNnHt9gq8n6dS2arBzfG2eEDv/viCyjggj0wcSaV2yUg==", + "dependencies": { + "@polkadot/rpc-core": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/util": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/api-derive": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-12.2.3.tgz", + "integrity": "sha512-zcQOuLoBeYXTMr2r9oPQiIJ7t4997eoQ1yM76KK2/2KTESKfJHus6nA0IK9fDk+c5vIdFKd/BJ0UukQ1AJiLLA==", + "dependencies": { + "@polkadot/api": "12.2.3", + "@polkadot/api-augment": "12.2.3", + "@polkadot/api-base": "12.2.3", + "@polkadot/rpc-core": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/keyring": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-13.0.2.tgz", + "integrity": "sha512-NeLbhyKDT5W8LI9seWTZGePxNTOVpDhv2018HSrEDwJq9Ie0C4TZhUf3KNERCkSveuThXjfQJMs+1CF33ZXPWw==", + "dependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/util-crypto": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/util-crypto": "13.0.2" + } + }, + "node_modules/@polkadot/networks": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-13.0.2.tgz", + "integrity": "sha512-ABAL+vug/gIwkdFEzeh87JoJd0YKrxSYg/HjUrZ+Zis2ucxQEKpvtCpJ34ku+YrjacBfVqIAkkwd3ZdIPGq9aQ==", + "dependencies": { + "@polkadot/util": "13.0.2", + "@substrate/ss58-registry": "^1.46.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-augment": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-12.2.3.tgz", + "integrity": "sha512-3V+Xp5cGb8hA0YZ4V4jXdC0POZGirQ63DkUnypmq86Fa1A7NCuVgD+s9ayOc8kNUMuKJIRKr3cLTj97S6f15lw==", + "dependencies": { + "@polkadot/rpc-core": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-12.2.3.tgz", + "integrity": "sha512-XJyPpwYBe+ijlivEKcRYRlQ5vx/CUXG0PZ23/TLKMRNlh5BVAC4HK/4dzBmOc3FT0ulOMbu7/TH+mk7ppQHrKg==", + "dependencies": { + "@polkadot/rpc-augment": "12.2.3", + "@polkadot/rpc-provider": "12.2.3", + "@polkadot/types": "12.2.3", + "@polkadot/util": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-12.2.3.tgz", + "integrity": "sha512-hzw6YGV+3daU49rsEPmdl/UDupAmc3lqBYN2gj7lxQCMSqYjBr0Pj1ScGJJXzlR8ZyiY97e/TGIW13W6ivmIGQ==", + "dependencies": { + "@polkadot/keyring": "^13.0.2", + "@polkadot/types": "12.2.3", + "@polkadot/types-support": "12.2.3", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "@polkadot/x-fetch": "^13.0.2", + "@polkadot/x-global": "^13.0.2", + "@polkadot/x-ws": "^13.0.2", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@substrate/connect": "0.8.10" + } + }, + "node_modules/@polkadot/types": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-12.2.3.tgz", + "integrity": "sha512-p6y3WdZBvdgT5+m+gvPaHXUaei1DQjMI9BxhzHS5FfOvDMSDf0uBacamtRmkdII5bJuUgGBYG9BjHic8yWu0/g==", + "dependencies": { + "@polkadot/keyring": "^13.0.2", + "@polkadot/types-augment": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/types-create": "12.2.3", + "@polkadot/util": "^13.0.2", + "@polkadot/util-crypto": "^13.0.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-augment": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-12.2.3.tgz", + "integrity": "sha512-RLHWl4TIgJqWFuGDgstKTYqB7EWGx4oJ5nzIdKCQgYAeOi+LFYXyZjE2ffhmX258VPsSXu4syeQpcBIEWns8kA==", + "dependencies": { + "@polkadot/types": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-codec": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-12.2.3.tgz", + "integrity": "sha512-oBHAEXyAMZ6ghEEgKW95cc4OFdkxiRKazx18Dk433sWk2HGkwGoKd9uK6xdelMgO1EnbBzZwc2epOhKH7rTEmQ==", + "dependencies": { + "@polkadot/util": "^13.0.2", + "@polkadot/x-bigint": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-create": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-12.2.3.tgz", + "integrity": "sha512-4XR04QFgKeHZEj7NyBK3A55EgzmGZtC175Hbq5y3+j8XV84amOOhVqj7gDQqnSyRMAtl7+HSsfpx3+Loh+4l+g==", + "dependencies": { + "@polkadot/types-codec": "12.2.3", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-known": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-12.2.3.tgz", + "integrity": "sha512-hB3fBlZ51dBaGRJf6ParvoqCSig9ovqjDgpFwysewXsc74GRoPPR7RQFw/hITxwdKL5ldyTZnBIGBxROiF86Tg==", + "dependencies": { + "@polkadot/networks": "^13.0.2", + "@polkadot/types": "12.2.3", + "@polkadot/types-codec": "12.2.3", + "@polkadot/types-create": "12.2.3", + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/types-support": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-12.2.3.tgz", + "integrity": "sha512-/YVZ0j126el/5e/BTrhw1SuDmlyV394zKak7LkYcAJ8IyDmT53cajMK2TQe03uVsE/vveligkYmJ24IEjZ+DRg==", + "dependencies": { + "@polkadot/util": "^13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-13.0.2.tgz", + "integrity": "sha512-/6bS9sfhJLhs8QuqWaR1eRapzfDdGC5XAQZEPL9NN5sTTA7HxWos8rVleai0UERm8QUMabjZ9rK9KpzbXl7ojg==", + "dependencies": { + "@polkadot/x-bigint": "13.0.2", + "@polkadot/x-global": "13.0.2", + "@polkadot/x-textdecoder": "13.0.2", + "@polkadot/x-textencoder": "13.0.2", + "@types/bn.js": "^5.1.5", + "bn.js": "^5.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/util-crypto": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.0.2.tgz", + "integrity": "sha512-woUsJJ6zd/caL7U+D30a5oM/+WK9iNI00Y8aNUHSj6Zq/KPzK9uqDBaLGWwlgrejoMQkxxiU2X0f2LzP15AtQg==", + "dependencies": { + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@polkadot/networks": "13.0.2", + "@polkadot/util": "13.0.2", + "@polkadot/wasm-crypto": "^7.3.2", + "@polkadot/wasm-util": "^7.3.2", + "@polkadot/x-bigint": "13.0.2", + "@polkadot/x-randomvalues": "13.0.2", + "@scure/base": "^1.1.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2" + } + }, + "node_modules/@polkadot/wasm-bridge": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz", + "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==", + "dependencies": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz", + "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==", + "dependencies": { + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-init": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz", + "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz", + "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==", + "dependencies": { + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz", + "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==", + "dependencies": { + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-util": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz", + "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-bigint": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-13.0.2.tgz", + "integrity": "sha512-h2jKT/UaxiEal8LhQeH6+GCjO7GwEqVAD2SNYteCOXff6yNttqAZYJuHZsndbVjVNwqRNf8D5q/zZkD0HUd6xQ==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-fetch": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-13.0.2.tgz", + "integrity": "sha512-B/gf9iriUr6za/Ui7zIFBfHz7UBZ68rJEIteWHx1UHRCZPcLqv+hgpev6xIGrkfFljI0/lI7IwtN2qy6HYzFBg==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-global": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-13.0.2.tgz", + "integrity": "sha512-OoNIXLB5y8vIKpk4R+XmpDPhipNXWSUvEwUnpQT7NAxNLmzgMq1FhbrwBWWPRNHPrQonp7mqxV/X+v5lv1HW/g==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.0.2.tgz", + "integrity": "sha512-SGj+L0H/7TWZtSmtkWlixO4DFzXDdluI0UscN2h285os2Ns8PnmBbue+iJ8PVSzpY1BOxd66gvkkpboPz+jXFQ==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "13.0.2", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.0.2.tgz", + "integrity": "sha512-mauglOkTJxLGmLwLc3J5Jlq/W+SHP53eiy3F8/8JxxfnXrZKgWoQXGpvXYPjFnMZj0MzDSy/6GjyGWnDCgdQFA==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-textencoder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.0.2.tgz", + "integrity": "sha512-Lq08H2OnVXj97uaOwg7tcmRS7a4VJYkHEeWO4FyEMOk6P6lU6W8OVNjjxG0se9PCEgmyZPUDbJI//1ynzP4cXw==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polkadot/x-ws": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-13.0.2.tgz", + "integrity": "sha512-nC5e2eY5D5ZR5teQOB7ib+dWLbmNws86cTz3BjKCalSMBBIn6i3V9ElgABpierBmnSJe9D94EyrH1BxdVfDxUg==", + "dependencies": { + "@polkadot/x-global": "13.0.2", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scure/base": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", + "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@substrate/connect": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.8.10.tgz", + "integrity": "sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w==", + "deprecated": "versions below 1.x are no longer maintained", + "optional": true, + "dependencies": { + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.4", + "@substrate/light-client-extension-helpers": "^0.0.6", + "smoldot": "2.0.22" + } + }, + "node_modules/@substrate/connect-extension-protocol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.0.0.tgz", + "integrity": "sha512-nKu8pDrE3LNCEgJjZe1iGXzaD6OSIDD4Xzz/yo4KO9mQ6LBvf49BVrt4qxBFGL6++NneLiWUZGoh+VSd4PyVIg==", + "optional": true + }, + "node_modules/@substrate/connect-known-chains": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.2.1.tgz", + "integrity": "sha512-2EShoa4DlwSqEFVjGpt+2zNDzIgHQ/lcteP/GRisRkZ6KZDhr7A0Q3eCcbmXVmO65sWLcPGs/eAZez8kc5/SOw==", + "optional": true + }, + "node_modules/@substrate/light-client-extension-helpers": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.6.tgz", + "integrity": "sha512-girltEuxQ1BvkJWmc8JJlk4ZxnlGXc/wkLcNguhY+UoDEMBK0LsdtfzQKIfrIehi4QdeSBlFEFBoI4RqPmsZzA==", + "optional": true, + "dependencies": { + "@polkadot-api/json-rpc-provider": "0.0.1", + "@polkadot-api/json-rpc-provider-proxy": "0.0.1", + "@polkadot-api/observable-client": "0.1.0", + "@polkadot-api/substrate-client": "0.0.1", + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.4", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "smoldot": "2.x" + } + }, + "node_modules/@substrate/ss58-registry": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.49.0.tgz", + "integrity": "sha512-leW6Ix4LD7XgvxT7+aobPWSw+WvPcN2Rxof1rmd0mNC5t2n99k1N7UNEvz7YEFSOUeHWmKIY7F5q8KeIqYoHfA==" + }, + "node_modules/@types/bn.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "dependencies": { + "undici-types": "~6.13.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scale-ts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/scale-ts/-/scale-ts-1.6.0.tgz", + "integrity": "sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q==", + "optional": true + }, + "node_modules/smoldot": { + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.22.tgz", + "integrity": "sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g==", + "optional": true, + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/undici-types": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tools/ci-k6/package.json b/tools/ci-k6/package.json new file mode 100644 index 00000000..e4edb222 --- /dev/null +++ b/tools/ci-k6/package.json @@ -0,0 +1,16 @@ +{ + "name": "ci-k6", + "version": "1.0.0", + "description": "", + "main": "main.mjs", + "scripts": { + "main": "node main.mjs", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "^12.2.3", + "@polkadot/keyring": "^13.0.2" + } +}