diff --git a/.gitignore b/.gitignore index 8230f87d6..b16add01c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ database/ ganache-data/ ipfs-data/ local-data/ -node-modules/ -node_modules/* +**/node_modules .idea .composedbRuntimeDefinition.json -openalex-importer \ No newline at end of file +openalex-importer diff --git a/ceramic-k8s/dev/recon_dev.yaml b/ceramic-k8s/dev/recon_dev.yaml new file mode 100644 index 000000000..b151c39ec --- /dev/null +++ b/ceramic-k8s/dev/recon_dev.yaml @@ -0,0 +1,398 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: retained-standard +provisioner: kubernetes.io/aws-ebs +reclaimPolicy: Retain +parameters: + type: gp3 +--- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: ceramic-one-env +data: + CERAMIC_ONE_STORE_DIR: "/data/ceramic-one" + CERAMIC_ONE_P2P_KEY_DIR: "/data/ceramic-one" + CERAMIC_ONE_BIND_ADDRESS: "0.0.0.0:5101" + CERAMIC_ONE_SWARM_ADDRESSES: "/ip4/0.0.0.0/tcp/4101,/ip4/0.0.0.0/udp/4101/quic-v1" + CERAMIC_ONE_METRICS_BIND_ADDRESS: "0.0.0.0:9465" + CERAMIC_ONE_LOCAL_NETWORK_ID: "0" + CERAMIC_ONE_NETWORK: "testnet-clay" + RUST_LOG: "info,ceramic_one=debug,multipart=error" + CERAMIC_ONE_LOG_FORMAT: "json" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: js-ceramic-env +data: + NODE_ENV: "production" + CERAMIC_NETWORK: "testnet-clay" + CERAMIC_STATE_STORE_PATH: "/js-ceramic-data/statestore" + CERAMIC_CORS_ALLOWED_ORIGINS: ".*" + CERAMIC_LOG_LEVEL: "2" + CERAMIC_IPFS_HOST: "http://localhost:5101" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ceramic-init +data: + # TODO: add anchor.authMethod and node.privateSeedUrl to config when duplicating for prod + daemon-config.json: | + { + "anchor": { + "ethereum-rpc-url": "${ANCHOR_RPC_URL}" + }, + "http-api": { + "cors-allowed-origins": [ + "${CERAMIC_CORS_ALLOWED_ORIGINS}" + ], + "admin-dids": [ + "${CERAMIC_ADMIN_DID}" + ] + }, + "ipfs": { + "mode": "remote", + "host": "${CERAMIC_IPFS_HOST}" + }, + "logger": { + "log-level": ${CERAMIC_LOG_LEVEL}, + "log-to-files": false + }, + "metrics": { + "metrics-exporter-enabled": false, + "prometheus-exporter-enabled": true, + "prometheus-exporter-port": 9464 + }, + "network": { + "name": "${CERAMIC_NETWORK}" + }, + "node": {}, + "state-store": { + "mode": "fs", + "local-directory": "${CERAMIC_STATE_STORE_PATH}" + }, + "indexing": { + "db": "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}", + "allow-queries-before-historical-sync": true + } + } + + js-ceramic-init.sh: | + #!/bin/bash + + set -eo pipefail + + source /vault/secrets/config + + export CERAMIC_ADMIN_DID=$(composedb did:from-private-key ${CERAMIC_ADMIN_PRIVATE_KEY}) + + envsubst \ + < /ceramic-init/daemon-config.json \ + > /config/daemon-config.json + + echo "Config file generated successfully" + + get-external-multiaddr.sh: | + #!/bin/bash + + set -eo pipefail + + MY_POD_INDEX=$(echo $HOSTNAME | grep -o '[0-9]\+$') + TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + NAMESPACE=default + + LB_DOMAIN=$(curl -s --header "Authorization: Bearer $TOKEN" \ + --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ + https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/services/ceramic-recon-dev-$MY_POD_INDEX-public \ + | grep -oE "k8s-.*.elb.*.amazonaws.com") + + if [ -z "$LB_DOMAIN" ]; then + echo "Failed to query for LB_DOMAIN" + exit 1 + fi + + EXTERNAL_MULTIADDR="/dns4/${LB_DOMAIN}/tcp/4101,/dns4/${LB_DOMAIN}/udp/4101/quic-v1" + echo -n "$EXTERNAL_MULTIADDR" > /config/external-multiaddr + echo "Wrote $EXTERNAL_MULTIADDR to /config/external-multiaddr" +--- + +# Stateful set for pairs of js-ceramic & ceramic-one +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: ceramic-recon-dev + labels: + App: CeramicReconDev +spec: + serviceName: ceramic-recon-dev + replicas: 1 + selector: + matchLabels: + App: CeramicReconDev + template: + metadata: + labels: + App: CeramicReconDev + annotations: + # inject vault agent container + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/role: app-vault-reader + # only run as init container; don't linger as a sidecar + vault.hashicorp.com/agent-pre-populate-only: "true" + # run before other init containers + vault.hashicorp.com/agent-init-first: "true" + vault.hashicorp.com/agent-inject-secret-config: "secrets/ceramic/dev" + vault.hashicorp.com/agent-inject-template-config: | + {{- with secret "secrets/ceramic/dev" -}} + {{- range $k, $v := .Data }} + export {{ $k }}={{ $v }} + {{- end }} + {{- end }} + spec: + serviceAccountName: "vault-auth" + containers: + - name: js-ceramic + image: ceramicnetwork/js-ceramic:6.5.0 + command: + - /js-ceramic/packages/cli/bin/ceramic.js + - daemon + - --config + - /config/daemon-config.json + env: + - name: BASH_ENV + value: /vault/secrets/config + envFrom: + - configMapRef: + name: js-ceramic-env + ports: + - name: http-api + containerPort: 7007 + protocol: TCP + - name: metrics + containerPort: 9464 + protocol: TCP + resources: + limits: + cpu: 2 + memory: 4Gi + requests: + cpu: 1 + memory: 4Gi + livenessProbe: + httpGet: + path: /api/v0/node/healthcheck + port: http-api + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: /api/v0/node/healthcheck + port: http-api + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 10 + volumeMounts: + - mountPath: /config + name: config-volume + - mountPath: /js-ceramic-data + name: js-ceramic-data + + - name: ceramic-one + image: public.ecr.aws/r5b3e0r5/3box/ceramic-one:0.39.0 + command: [ "bash", "-c" ] + args: [ 'ceramic-one daemon --external-swarm-addresses="$(< /config/external-multiaddr)"'] + env: + - name: BASH_ENV + value: /vault/secrets/config + envFrom: + - configMapRef: + name: ceramic-one-env + ports: + - containerPort: 4101 + name: swarm-tcp + protocol: TCP + - containerPort: 4101 + name: swarm-udp + protocol: UDP + - containerPort: 5101 + name: rpc + protocol: TCP + - containerPort: 9465 + name: metrics + protocol: TCP + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 1 + memory: 1Gi + livenessProbe: + httpGet: + path: /ceramic/liveness + port: rpc + scheme: HTTP + readinessProbe: + httpGet: + path: /ceramic/liveness + port: rpc + scheme: HTTP + volumeMounts: + - name: ceramic-one-data + mountPath: /data/ceramic-one + - name: config-volume + mountPath: /config + + initContainers: + - name: init-ceramic-config + command: [ "bash", "-c" ] + args: [ "/ceramic-init/js-ceramic-init.sh && /ceramic-init/get-external-multiaddr.sh" ] + env: + - name: BASH_ENV + value: /vault/secrets/config + envFrom: + - configMapRef: + name: js-ceramic-env + image: ceramicnetwork/composedb-cli + imagePullPolicy: Always + volumeMounts: + - mountPath: /config + name: config-volume + - mountPath: /ceramic-init + name: ceramic-init + + volumes: + # holds the config file during the lifetime of the pod + - emptyDir: {} + name: config-volume + # set 755 on files to allow executing init script + - configMap: + defaultMode: 493 + name: ceramic-init + name: ceramic-init + - configMap: + name: ceramic-one-env + name: ceramic-one-env + - name: js-ceramic-data + persistentVolumeClaim: + claimName: js-ceramic-data + + volumeClaimTemplates: + - metadata: + name: js-ceramic-data + labels: + App: CeramicReconDev + spec: + accessModes: + - ReadWriteOnce + storageClassName: retained-standard + resources: + requests: + storage: 100Gi + + - metadata: + name: ceramic-one-data + labels: + App: CeramicReconDev + spec: + accessModes: + - ReadWriteOnce + storageClassName: retained-standard + resources: + requests: + storage: 100Gi +--- + +# headless service for StatefulSet +apiVersion: v1 +kind: Service +metadata: + name: ceramic-recon-dev + labels: + App: CeramicReconDev +spec: + clusterIP: None + selector: + App: CeramicReconDev + ports: + - port: 7007 + name: http-api + - port: 4101 + name: swarm-tcp + protocol: TCP + - port: 4101 + name: swarm-udp + protocol: UDP + - port: 5101 + name: rpc + - port: 9464 + name: js-metrics + - port: 9465 + name: one-metrics +--- + +# individual services (add/remove when scaling) +# 0 +apiVersion: v1 +kind: Service +metadata: + name: ceramic-recon-dev-0-internal + labels: + App: CeramicReconDev +spec: + type: ClusterIP + selector: + App: CeramicReconDev + statefulset.kubernetes.io/pod-name: ceramic-recon-dev-0 + ports: + - port: 80 + name: http-api + targetPort: http-api + - port: 4101 + name: swarm-tcp + protocol: TCP + targetPort: swarm-tcp + - port: 4101 + name: swarm-udp + protocol: UDP + targetPort: swarm-udp + - port: 5101 + name: rpc + targetPort: rpc + - port: 9464 + name: js-metrics + targetPort: js-metrics + - port: 9465 + name: one-metrics + targetPort: one-metrics +--- +apiVersion: v1 +kind: Service +metadata: + name: ceramic-recon-dev-0-public +spec: + type: LoadBalancer + selector: + App: CeramicReconDev + statefulset.kubernetes.io/pod-name: ceramic-recon-dev-0 + ports: + - port: 80 + name: http-api + targetPort: http-api + - port: 4101 + name: swarm-tcp + protocol: TCP + targetPort: swarm-tcp + - port: 4101 + name: swarm-udp + protocol: UDP + targetPort: swarm-udp diff --git a/desci-contracts/scripts/alias-registry/manualUpgrade.mjs b/desci-contracts/scripts/alias-registry/manualUpgrade.mjs new file mode 100644 index 000000000..216d9b4e9 --- /dev/null +++ b/desci-contracts/scripts/alias-registry/manualUpgrade.mjs @@ -0,0 +1,73 @@ +/** + * MANUAL DPID UPGRADE + * + * Manually upgrade a legacy dPID to an alias, e.g., bind a streamID + * to a dPID in `registry` and `reverseRegistry` mappings. + * + * Notes: + * - This prevents (unpriviligied) binding of this stream ID to another dPID + * + * Required arguments (env variables): + * 1. ENV - dev or prod + * 2. REGISTRY_ADDRESS - Address of existing alias registry (proxy) contract + * 3. PRIVATE_KEY - Owner/admin identity (see hardhat.config.ts) + * 4. DPID - The dPID to bind + * 5. STREAM_ID - The streamID to bind + * 6. CONFIRM - Set "yes" to actually execute, otherwise run only checks + */ +import hardhat from "hardhat"; +const { ethers } = hardhat; + +const ENV = process.env.ENV; +if (!(ENV === "dev" || ENV === "prod")) { + throw new Error('ENV unset (wanted "dev" or "prod")'); +}; + +const CERAMIC_API = `https://ceramic-${ENV}.desci.com/api/v0/streams/`; + +const REGISTRY_ADDRESS = process.env.REGISTRY_ADDRESS; +if (!REGISTRY_ADDRESS) { + throw new Error("REGISTRY_ADDRESS unset"); +}; + +const DPID = process.env.DPID; +if (!DPID) { + throw new Error("DPID unset"); +}; + +const STREAM_ID = process.env.STREAM_ID; +if (!STREAM_ID) { + throw new Error("STREAM_ID unset"); +}; + +const DpidAliasRegistryFactory = await ethers.getContractFactory("DpidAliasRegistry"); +const registry = DpidAliasRegistryFactory.attach(REGISTRY_ADDRESS); + +const dpidLookup = await registry.resolve(DPID); +const freeDpid = dpidLookup === ""; +console.log(`➡ dPID ${DPID} unbound: ${freeDpid ? "✅" : "❌"}`); + +const reverseLookup = await registry.find(STREAM_ID); +const freeStreamID = reverseLookup.toNumber() === 0; +console.log(`➡ Stream ${STREAM_ID} unbound: ${freeStreamID ? "✅" : "❌"}`); + +const [legacyOwner, _versions ] = await registry.legacyLookup(DPID); +const res = await fetch(CERAMIC_API + STREAM_ID); +const body = await res.json(); +const streamController = res.ok + ? body.state.metadata.controllers[0] + : "UNKNOWN"; +const sameOwner = legacyOwner.toLowerCase() === streamController.split(":").pop(); +console.log( + `➡ Same owner: ${sameOwner ? "✅" : "❌"}`, + { legacyOwner, streamController } +); + +console.log(); +if (process.env.CONFIRM === "yes") { + const setNextDpid = await registry.upgradeDpid(NEXT_DPID) + await setNextDpid.wait(); + console.log(`🆙 Bound ${DPID} to ${STREAM_ID}`); +} else { + console.log(`🙅 Skipping binding ${DPID} to ${STREAM_ID} (set CONFIRM to execute)`); +} diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile index d9aad0a01..0c0342243 100644 --- a/desci-media-isolated/Dockerfile +++ b/desci-media-isolated/Dockerfile @@ -1,5 +1,5 @@ -FROM docker.io/node:20.9.0 as base +FROM docker.io/node:20.9.0 AS base # Install dumb-init so we can use it as PID 1 @@ -16,7 +16,7 @@ WORKDIR /usr/src/app COPY tsconfig.json . COPY package*.json ./ -FROM base as dev +FROM base AS dev RUN --mount=type=cache,target=/usr/src/app/.npm \ npm set cache /usr/src/app/.npm && \ @@ -32,7 +32,7 @@ EXPOSE 9777 ENTRYPOINT ["/usr/src/app/scripts/containerInitDev.sh"] CMD ["dumb-init", "npx", "tsx","watch", "--clear-screen=false", "--env-file=.env", "--inspect=0.0.0.0:9777", "src/index.ts"] -FROM base as production +FROM base AS production # Cache mounts for faster builds, prod env for better express perf RUN --mount=type=cache,target=/usr/src/app/.npm \ npm set cache /usr/src/app/.npm && \ diff --git a/desci-models/package-lock.json b/desci-models/package-lock.json deleted file mode 100644 index a4233902d..000000000 --- a/desci-models/package-lock.json +++ /dev/null @@ -1,3498 +0,0 @@ -{ - "name": "@desci-labs/desci-models", - "version": "0.1.21", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@desci-labs/desci-models", - "version": "0.1.21", - "license": "MIT", - "dependencies": { - "jsonld": "^8.1.1", - "schema-dts": "^1.1.2", - "typescript": "^4.9.4", - "util": "^0.12.5", - "zod": "^3.20.2" - }, - "devDependencies": { - "@types/chai": "^4.3.4", - "@types/jsonld": "^1.5.8", - "@types/mocha": "^10.0.1", - "@types/node": "^16.11.12", - "chai": "^4.3.7", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "ts-interface-builder": "^0.3.3", - "ts-interface-checker": "^1.0.2", - "ts-node": "^10.9.1" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.20.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", - "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.20.11", - "@babel/helpers": "^7.20.7", - "@babel/parser": "^7.20.7", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.12", - "@babel/types": "^7.20.7", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", - "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", - "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.10", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", - "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", - "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.20.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", - "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", - "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@digitalbazaar/http-client": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-3.4.0.tgz", - "integrity": "sha512-B/T3Xlo5UjPkoAqX/DQOAF2D5khgNZJZhoQ1d1b3ykYd2XnwOQ6srz1T+SsWVfjbXyLajN7j/nfKy7QiUofN+A==", - "license": "BSD-3-Clause", - "dependencies": { - "ky": "^0.33.3", - "ky-universal": "^0.11.0", - "undici": "^5.21.2" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonld": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.8.tgz", - "integrity": "sha512-4l5t/jDnJpqZ+i7CLTTgPcT5BYXnAnwJupb07aAokPufCV0SjDHcwctUkSTuhIuSU9yHok+WOOngIGCtpL96gw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "16.18.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", - "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", - "dev": true, - "license": "MIT" - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001442", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", - "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/canonicalize": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", - "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==", - "license": "Apache-2.0" - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "license": "MIT" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true, - "license": "ISC" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "license": "ISC", - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonld": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-8.1.1.tgz", - "integrity": "sha512-TbtV1hlnoDYxbscazbxcS7seDGV+pc0yktxpMySh0OBFvnLw/TIth0jiQtP/9r+ywuCbtj10XjDNBIkRgiyeUg==", - "license": "BSD-3-Clause", - "dependencies": { - "@digitalbazaar/http-client": "^3.2.0", - "canonicalize": "^1.0.1", - "lru-cache": "^6.0.0", - "rdf-canonize": "^3.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/ky": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", - "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/ky-universal": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.11.0.tgz", - "integrity": "sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "node-fetch": "^3.2.10" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky-universal?sponsor=1" - }, - "peerDependencies": { - "ky": ">=0.31.4", - "web-streams-polyfill": ">=3.2.1" - }, - "peerDependenciesMeta": { - "web-streams-polyfill": { - "optional": true - } - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "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" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "license": "MIT", - "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/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rdf-canonize": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rdf-canonize/-/rdf-canonize-3.3.0.tgz", - "integrity": "sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==", - "license": "BSD-3-Clause", - "dependencies": { - "setimmediate": "^1.0.5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "license": "ISC", - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "license": "ISC" - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/schema-dts": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.2.tgz", - "integrity": "sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==", - "license": "Apache-2.0", - "peerDependencies": { - "typescript": ">=4.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-interface-builder": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/ts-interface-builder/-/ts-interface-builder-0.3.3.tgz", - "integrity": "sha512-WHQwVBy0+Sv/jcHhKlyFgTyEVTM0GEPEw+gLmOYlZiJC1/eh5ah2EHSw7o+RUrl2grjEAMU6MTOItCuQIVJvnQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "commander": "^2.12.2", - "fs-extra": "^4.0.3", - "glob": "^7.1.6", - "typescript": "^3.0.0" - }, - "bin": { - "ts-interface-builder": "bin/ts-interface-builder" - } - }, - "node_modules/ts-interface-builder/node_modules/typescript": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", - "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-1.0.2.tgz", - "integrity": "sha512-4IKKvhZRXhvtYF/mtu+OCfBqJKV6LczUq4kQYcpT+iSB7++R9+giWnp2ecwWMIcnG16btVOkXFnoxLSYMN1Q1g==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/undici": { - "version": "5.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.2.tgz", - "integrity": "sha512-f6pTQ9RF4DQtwoWSaC42P/NKlUjvezVvd9r155ohqkwFNRyBKM3f3pcty3ouusefNRyM25XhIQEbeQ46sZDJfQ==", - "license": "MIT", - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=12.18" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/yargs/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.20.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.20.2.tgz", - "integrity": "sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/desci-models/package.json b/desci-models/package.json index efd265dd5..f36c26646 100644 --- a/desci-models/package.json +++ b/desci-models/package.json @@ -1,6 +1,6 @@ { "name": "@desci-labs/desci-models", - "version": "0.2.11", + "version": "0.2.12", "description": "Data models for DeSci Nodes", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -38,4 +38,4 @@ "ts-node": "^10.9.1", "typescript": "^4.9.4" } -} \ No newline at end of file +} diff --git a/desci-models/src/actions.ts b/desci-models/src/actions.ts new file mode 100644 index 000000000..861764ec0 --- /dev/null +++ b/desci-models/src/actions.ts @@ -0,0 +1,57 @@ +export enum AvailableUserActionLogTypes { + publishStep = 'publishStep', + btnPublishActivityBar = 'btnPublishActivityBar', + btnSidebarNavigation = 'btnSidebarNavigation', + tabProfilePublishedNodes = 'tabProfilePublishedNodes', + tabProfileSharedNodes = 'tabProfileSharedNodes', + tabProfileAllNodes = 'tabProfileAllNodes', + btnProfileCreateNewResearchObject = 'btnProfileCreateNewResearchObject', + btnProfileCreateNewSubmissionPackage = 'btnProfileCreateNewSubmissionPackage', + btnDownloadData = 'btnDownloadData', + btnDownloadManuscript = 'btnDownloadManuscript', + btnShare = 'btnShare', + btnPublish = 'btnPublish', + btnAddComponentFab = 'btnAddComponentFab', + btnAddComponentDrive = 'btnAddComponentDrive', + btnAddComponentDriveNewComponent = 'btnAddComponentDriveNewComponent', + btnAddComponentDriveNewFolder = 'btnAddComponentDriveNewFolder', + driveNavigateBreadcrumb = 'driveNavigateBreadcrumb', + btnFigureAnnotate = 'btnFigureAnnotate', + btnContinuePublish = 'btnContinuePublish', + btnReviewBeforePublish = 'btnReviewBeforePublish', + dismissCommitAdditionalInfo = 'dismissCommitAdditionalInfo', + dismissCommitStatus = 'dismissCommitStatus', + completePublish = 'completePublish', + btnSignPublish = 'btnSignPublish', + commitPanelDismiss = 'commitPanelDismiss', + viewWalletSettings = 'viewWalletSettings', + walletMoreOptions = 'walletMoreOptions', + walletSwitchChain = 'walletSwitchChain', + walletClickCard = 'walletClickCard', + walletError = 'walletError', + walletDisconnect = 'walletDisconnect', + connectWallet = 'connectWallet', + btnComponentCardCite = 'btnComponentCardCite', + btnComponentCardViewFile = 'btnComponentCardViewFile', + btnComponentCardUse = 'btnComponentCardUse', + btnComponentCardViewLink = 'btnComponentCardViewLink', + btnComponentCardViewMetadata = 'btnComponentCardViewMetadata', + viewDrive = 'viewDrive', + btnDriveCite = 'btnDriveCite', + btnDriveUse = 'btnDriveUse', + btnDriveStarToggle = 'btnDriveStarToggle', + saveMetadata = 'saveMetadata', + btnInspectMetadata = 'btnInspectMetadata', + ctxDriveMove = 'ctxDriveMove', + ctxDriveRename = 'ctxDriveRename', + ctxDrivePreview = 'ctxDrivePreview', + ctxDriveDownload = 'ctxDriveDownload', + ctxDriveDelete = 'ctxDriveDelete', + ctxDriveAssignType = 'ctxDriveAssignType', + ctxDriveEditMetadata = 'ctxDriveEditMetadata', + btnCreateNewNode = 'btnCreateNewNode', + btnCreateNodeModalSave = 'btnCreateNodeModalSave', + errNodeCreate = 'errNodeCreate', + viewedNode = 'viewedNode', + search = 'search', +} diff --git a/desci-models/src/index.ts b/desci-models/src/index.ts index 1a66f4338..f69a43562 100644 --- a/desci-models/src/index.ts +++ b/desci-models/src/index.ts @@ -5,3 +5,4 @@ export * from './trees/treeTools'; export * from './trees/treeTypes'; export * from './automerge'; export * from './fields'; +export * from './actions'; diff --git a/desci-models/yarn.lock b/desci-models/yarn.lock index 5e0ec3a03..c72e97c15 100644 --- a/desci-models/yarn.lock +++ b/desci-models/yarn.lock @@ -22,7 +22,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz" integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== -"@babel/core@^7.0.0", "@babel/core@^7.7.5": +"@babel/core@^7.7.5": version "7.20.12" resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz" integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== @@ -239,7 +239,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@3.1.0": +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== @@ -249,19 +249,11 @@ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -270,6 +262,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" @@ -305,7 +305,7 @@ resolved "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz" integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== -"@types/node@*", "@types/node@^16.11.12": +"@types/node@^16.11.12": version "16.18.23" resolved "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz" integrity sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g== @@ -438,7 +438,7 @@ browser-stdout@1.3.1: resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.21.3, "browserslist@>= 4.21.0": +browserslist@^4.21.3: version "4.21.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== @@ -572,16 +572,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + commander@^2.12.2: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" @@ -621,7 +621,7 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== -debug@^4.1.0, debug@^4.1.1, debug@4.3.4: +debug@4.3.4, debug@^4.1.0, debug@^4.1.1: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -652,16 +652,16 @@ default-require-extensions@^3.0.0: dependencies: strip-bom "^4.0.0" -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - diff@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + electron-to-chromium@^1.4.251: version "1.4.284" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz" @@ -682,16 +682,16 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + esprima@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -726,14 +726,6 @@ find-cache-dir@^3.2.0: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" @@ -742,6 +734,14 @@ find-up@5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" @@ -781,6 +781,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -808,27 +813,27 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== +glob@7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.1.1" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -1017,6 +1022,13 @@ js-tokens@^4.0.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" @@ -1025,13 +1037,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -1067,7 +1072,7 @@ ky-universal@^0.11.0: abort-controller "^3.0.0" node-fetch "^3.2.10" -ky@^0.33.3, ky@>=0.31.4: +ky@^0.33.3: version "0.33.3" resolved "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz" integrity sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw== @@ -1132,13 +1137,6 @@ make-error@^1.1.1: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - minimatch@5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz" @@ -1146,6 +1144,13 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + mocha@^10.2.0: version "10.2.0" resolved "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz" @@ -1505,6 +1510,13 @@ strip-json-comments@3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" @@ -1519,13 +1531,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" @@ -1603,7 +1608,7 @@ typescript@^3.0.0: resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -typescript@^4.9.4, typescript@>=2.7, typescript@>=4.1.0: +typescript@^4.9.4: version "4.9.4" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz" integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== @@ -1638,7 +1643,7 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -web-streams-polyfill@^3.0.3, web-streams-polyfill@>=3.2.1: +web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz" integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== @@ -1713,6 +1718,11 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" @@ -1726,11 +1736,6 @@ yargs-parser@^20.2.2: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" @@ -1741,6 +1746,19 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yargs@^15.0.2: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" @@ -1758,19 +1776,6 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" diff --git a/desci-repo/Dockerfile b/desci-repo/Dockerfile index df909dce2..c4739b9e7 100644 --- a/desci-repo/Dockerfile +++ b/desci-repo/Dockerfile @@ -9,9 +9,10 @@ WORKDIR /app RUN mkdir /app/repo-tmp RUN mkdir -p /app/desci-repo/repo-tmp -COPY . . - +COPY package.json . RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install + +COPY . . RUN yarn build # server api @@ -20,4 +21,4 @@ EXPOSE 5484 # websocket EXPOSE 5445 -CMD [ "yarn", "start" ] \ No newline at end of file +CMD [ "yarn", "start" ] diff --git a/desci-repo/kubernetes/deployment_dev.yaml b/desci-repo/kubernetes/deployment_dev.yaml index da90a38c1..e106c0575 100644 --- a/desci-repo/kubernetes/deployment_dev.yaml +++ b/desci-repo/kubernetes/deployment_dev.yaml @@ -49,7 +49,7 @@ spec: export DATABASE_URL={{ .Data.DATABASE_URL }} export IPFS_RESOLVER_OVERRIDE={{ .Data.IPFS_RESOLVER_OVERRIDE }} export DESCI_SERVER_URL={{ .Data.DESCI_SERVER_URL }} - + export PINO_LOG_LEVEL=info {{- end -}} labels: App: DesciRepoServerDev @@ -67,9 +67,9 @@ spec: name: ws-api resources: limits: - cpu: '4' + cpu: '4.0' memory: 16Gi requests: - cpu: '1' + cpu: '2.0' memory: 8Gi serviceAccountName: 'vault-auth' diff --git a/desci-repo/kubernetes/deployment_prod.yaml b/desci-repo/kubernetes/deployment_prod.yaml index 799d505d6..a41ffe76d 100755 --- a/desci-repo/kubernetes/deployment_prod.yaml +++ b/desci-repo/kubernetes/deployment_prod.yaml @@ -50,7 +50,7 @@ spec: export DATABASE_URL={{ .Data.DATABASE_URL }} export IPFS_RESOLVER_OVERRIDE={{ .Data.IPFS_RESOLVER_OVERRIDE }} export DESCI_SERVER_URL={{ .Data.DESCI_SERVER_URL }} - + export PINO_LOG_LEVEL=info {{- end -}} labels: App: DesciRepoServerProd diff --git a/desci-repo/kubernetes/deployment_staging.yaml b/desci-repo/kubernetes/deployment_staging.yaml index caa89543b..d8f6f5094 100644 --- a/desci-repo/kubernetes/deployment_staging.yaml +++ b/desci-repo/kubernetes/deployment_staging.yaml @@ -50,7 +50,7 @@ spec: export DATABASE_URL={{ .Data.DATABASE_URL }} export IPFS_RESOLVER_OVERRIDE={{ .Data.IPFS_RESOLVER_OVERRIDE }} export DESCI_SERVER_URL={{ .Data.DESCI_SERVER_URL }} - + export PINO_LOG_LEVEL=info {{- end -}} labels: App: DesciRepoServerStaging diff --git a/desci-repo/src/controllers/nodes/documents.ts b/desci-repo/src/controllers/nodes/documents.ts index 3188a8cce..f4f8f271c 100644 --- a/desci-repo/src/controllers/nodes/documents.ts +++ b/desci-repo/src/controllers/nodes/documents.ts @@ -67,16 +67,12 @@ export const createNodeDocument = async function (req: Request, res: Response) { export const getLatestNodeManifest = async function (req: Request, res: Response) { const logger = parentLogger.child({ module: 'getLatestNodeManifest', params: req.params }); - logger.trace('getLatestNodeManifest'); const { uuid } = req.params; const { documentId } = req.query; const getDocument = async (documentId: DocumentId) => { const automergeUrl = getAutomergeUrl(documentId); const handle = backendRepo.find(automergeUrl as AutomergeUrl); - logger.trace({ uuid, automergeUrl }, 'Document Handle retrieved'); - - logger.trace({ uuid, automergeUrl }, 'Get DOCUMENT'); const document = await handle.doc(); logger.trace({ uuid, automergeUrl }, 'DOCUMENT Retrieved'); return document; @@ -86,10 +82,8 @@ export const getLatestNodeManifest = async function (req: Request, res: Response // todo: add support for documentId params and skip querying node // fast track call if documentId is available if (documentId) { - logger.trace({ documentId }, 'Fast track using documentId'); const document = await getDocument(documentId as DocumentId); if (document) res.status(200).send({ ok: true, document }); - logger.trace({ document: !!document }, 'Fast tracked call using documentId'); return; } @@ -110,19 +104,6 @@ export const getLatestNodeManifest = async function (req: Request, res: Response return; } - logger.info( - { manifestDocumentId: node.manifestDocumentId, url: getAutomergeUrl(node.manifestDocumentId) }, - 'Node manifestDocumentId', - ); - - // const automergeUrl = getAutomergeUrl(node.manifestDocumentId as DocumentId); - // const handle = backendRepo.find(automergeUrl as AutomergeUrl); - // logger.trace({ uuid, automergeUrl }, 'Document Handle retrieved'); - - // logger.trace({ uuid, automergeUrl }, 'Get DOCUMENT'); - // const document = await handle.doc(); - // logger.trace({ uuid, automergeUrl }, 'DOCUMENT Retrieved'); - if (!node.manifestDocumentId) { res.status(404).send({ ok: false, message: `node: ${uuid} has no documentId: ${node.manifestDocumentId}` }); return; @@ -130,7 +111,6 @@ export const getLatestNodeManifest = async function (req: Request, res: Response const document = await getDocument(node.manifestDocumentId as DocumentId); - logger.info({ manifest: document?.manifest }, '[getLatestNodeManifest::END]'); res.status(200).send({ ok: true, document }); } catch (err) { logger.error({ err }, 'Error'); @@ -158,7 +138,6 @@ export const dispatchDocumentChange = async function (req: RequestWithNode, res: const dispatchChange = getDocumentUpdater(documentId); - logger.info({ actions }, 'Actions'); for (const action of actions) { document = await dispatchChange(action); } @@ -169,7 +148,6 @@ export const dispatchDocumentChange = async function (req: RequestWithNode, res: } res.status(200).send({ ok: true, document }); - logger.trace('END'); } catch (err) { logger.error({ err }, 'Error [dispatchDocumentChange]'); res.status(500).send({ ok: false, message: JSON.stringify(err) }); @@ -178,7 +156,7 @@ export const dispatchDocumentChange = async function (req: RequestWithNode, res: export const dispatchDocumentActions = async function (req: RequestWithNode, res: Response) { const logger = parentLogger.child({ module: 'dispatchDocumentActions' }); - logger.info({ body: req.body }, 'START [dispatchDocumentActions]'); + // logger.info({ body: req.body }, 'START [dispatchDocumentActions]'); try { if (!(req.body.uuid && req.body.documentId && req.body.actions)) { logger.error({ body: req.body }, 'Invalid data'); @@ -190,20 +168,19 @@ export const dispatchDocumentActions = async function (req: RequestWithNode, res const documentId = req.body.documentId as DocumentId; if (!(actions && actions.length > 0)) { - logger.error({ actions }, 'No actions to dispatch'); + logger.error({ body: req.body }, 'No actions to dispatch'); res.status(400).send({ ok: false, message: 'No actions to dispatch' }); return; } const validatedActions = await actionsSchema.parseAsync(actions); - logger.info({ validatedActions }, 'Actions validated'); + logger.trace({ validatedActions }, 'Actions validated'); let document: Doc | undefined; const dispatchChange = getDocumentUpdater(documentId); for (const action of actions) { - logger.info({ action }, '[AUTOMERGE]::[dispatch Update]'); document = await dispatchChange(action); } @@ -213,7 +190,6 @@ export const dispatchDocumentActions = async function (req: RequestWithNode, res return; } - logger.info('END [dispatchDocumentActions]', { document }); res.status(200).send({ ok: true, document }); } catch (err) { logger.error(err, 'Error [dispatchDocumentChange]'); diff --git a/desci-repo/src/lib/PostgresStorageAdapter.ts b/desci-repo/src/lib/PostgresStorageAdapter.ts index 498a390da..f8c3876c4 100644 --- a/desci-repo/src/lib/PostgresStorageAdapter.ts +++ b/desci-repo/src/lib/PostgresStorageAdapter.ts @@ -21,7 +21,7 @@ export class PostgresStorageAdapter extends StorageAdapter { try { const result = await query(`SELECT * FROM "${this.tableName}" WHERE key = $1`, [key]); - logger.info({ value: result?.length, key }, '[LOAD DOCUMENT]::'); + logger.trace({ value: result?.length, key }, '[LOAD DOCUMENT]::'); const response = result?.[0]; // MUST RETURN UNDEFINED! @@ -38,7 +38,7 @@ export class PostgresStorageAdapter extends StorageAdapter { this.cache[key] = binary; try { - logger.info({ action: 'Save', key }, 'PostgresStorageAdaptser::Save'); + logger.trace({ action: 'Save', key }, 'PostgresStorageAdaptser::Save'); await query( `INSERT INTO "${this.tableName}" (key, value) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET value = $2 RETURNING key`, @@ -55,7 +55,7 @@ export class PostgresStorageAdapter extends StorageAdapter { delete this.cache[key]; try { - logger.info({ action: 'Remove', key }, 'PostgresStorageAdapter::Remove'); + logger.trace({ action: 'Remove', key }, 'PostgresStorageAdapter::Remove'); await query(`DELETE FROM "${this.tableName}" WHERE key = $1 RETURNING key`, [key]); } catch (e) { logger.error({ e, key }, 'PostgresStorageAdapter::Remove ==> Error deleting document'); @@ -81,7 +81,7 @@ export class PostgresStorageAdapter extends StorageAdapter { const key = getKey(keyPrefix); this.cachedKeys(keyPrefix).forEach((key) => delete this.cache[key]); try { - logger.info({ key, keyPrefix }, 'DELETE DOCUMENT RANGE'); + logger.trace({ key, keyPrefix }, 'DELETE DOCUMENT RANGE'); const result = await query(`DELETE FROM "${this.tableName}" WHERE key LIKE $1 RETURNING key`, [`${key}%`]); console.log({ result, key }, 'DELETED MANY RANGE'); } catch (e) { @@ -95,9 +95,9 @@ export class PostgresStorageAdapter extends StorageAdapter { } private async loadRangeKeys(keyPrefix: string[]): Promise { - logger.info({ keyPrefix }, 'LoadRange Keys'); + logger.trace({ keyPrefix }, 'LoadRange Keys'); const response = await query(`SELECT key FROM "${this.tableName}" WHERE key LIKE $1`, [`${keyPrefix}%`]); - logger.info({ keyPrefix, response: response?.length }, '[LOADED RANGE Keys]'); + logger.trace({ keyPrefix, response: response?.length }, '[LOADED RANGE Keys]'); return response ? response.map((row) => row.key) : []; } diff --git a/desci-repo/src/middleware/ensureApiKey.ts b/desci-repo/src/middleware/ensureApiKey.ts index b538037bf..28433065e 100644 --- a/desci-repo/src/middleware/ensureApiKey.ts +++ b/desci-repo/src/middleware/ensureApiKey.ts @@ -5,7 +5,7 @@ const REPO_SERVICE_API_KEY = process.env.REPO_SERVICE_SECRET_KEY; export const ensureApiKey = async (req: Request, res: Response, next: NextFunction) => { const apiKey = req.headers['x-api-key']; - logger.info({ module: 'EnsureApiKey', apiKeyLength: apiKey?.length, hostname: req.hostname }, 'VERIFY API KEY from'); + logger.trace({ module: 'EnsureApiKey', apiKeyLength: apiKey?.length, hostname: req.hostname }, 'VERIFY API KEY from'); if (!apiKey || apiKey !== REPO_SERVICE_API_KEY) { res.sendStatus(401); return; diff --git a/desci-repo/src/middleware/guard.ts b/desci-repo/src/middleware/guard.ts index b10576409..49665ae7b 100644 --- a/desci-repo/src/middleware/guard.ts +++ b/desci-repo/src/middleware/guard.ts @@ -30,11 +30,9 @@ export interface RequestWithNode extends RequestWithUser { } export const ensureNodeAccess = async (req: RequestWithUser, res: Response, next: NextFunction) => { - logger.info('START EnsureNodeAccess'); const token = await extractAuthToken(req); if (!token) { - logger.trace('Token not found'); res.status(401).send({ ok: false, message: 'Unauthorized' }); return; } @@ -42,7 +40,6 @@ export const ensureNodeAccess = async (req: RequestWithUser, res: Response, next const user = await extractUserFromToken(token); if (!(user && user.id > 0)) { - logger.trace('User not found'); res.status(401).send({ ok: false, message: 'Unauthorized' }); return; } @@ -59,9 +56,9 @@ export const ensureNodeAccess = async (req: RequestWithUser, res: Response, next const rows = await query('SELECT * FROM "Node" WHERE uuid = $1 AND ownerId = $2', [uuid, user.id]); const node = rows?.[0]; - logger.info({ email: hideEmail(user.email), uuid, node }, '[EnsureNodeAccess]:: => '); + logger.trace({ email: hideEmail(user.email), uuid, node }, '[EnsureNodeAccess]:: => '); if (!node) { - logger.info({ uuid, user }, `Node not found ${req.params}`); + logger.trace({ uuid, user }, `Node not found ${req.params}`); res.status(401).send({ message: 'Unauthorized' }); return; } diff --git a/desci-repo/src/middleware/permissions.ts b/desci-repo/src/middleware/permissions.ts index 553ea5fac..0d63c25b4 100644 --- a/desci-repo/src/middleware/permissions.ts +++ b/desci-repo/src/middleware/permissions.ts @@ -49,7 +49,7 @@ export const extractAuthToken = async (request: ExpressRequest | Request) => { if (authHeader) { token = authHeader.split(' ')[1]; } - logger.info({ module: 'Permissions::extractAuthToken', authHeaderLength: authHeader?.length || 0 }, 'Request'); + logger.trace({ module: 'Permissions::extractAuthToken', authHeaderLength: authHeader?.length || 0 }, 'Request'); // If auth token wasn't found in the header, try retrieve from cookies if (!token && request['cookies']) { diff --git a/desci-repo/src/repo.ts b/desci-repo/src/repo.ts index ca6ea0c83..968c13e50 100644 --- a/desci-repo/src/repo.ts +++ b/desci-repo/src/repo.ts @@ -29,10 +29,8 @@ const config: RepoConfig = { sharePolicy: async (peerId, documentId) => { try { if (!documentId) { - logger.warn({ peerId }, 'SharePolicy: Document ID NOT found'); + logger.trace({ peerId }, 'SharePolicy: Document ID NOT found'); return false; - } else { - logger.trace({ peerId, documentId }, 'SharePolicy: Document found'); } // peer format: `peer-[user#id]:[unique string combination] if (peerId.toString().length < 8) { diff --git a/desci-repo/src/services/manifestRepo.ts b/desci-repo/src/services/manifestRepo.ts index ee7845bc3..b82b6e1d0 100644 --- a/desci-repo/src/services/manifestRepo.ts +++ b/desci-repo/src/services/manifestRepo.ts @@ -50,8 +50,8 @@ export const getDocumentUpdater = (documentId: DocumentId) => { } const heads = getHeads(latestDocument); - logger.info({ heads }, `Document Heads`); - logger.info({ action }, `DocumentUpdater::Dispatched`); + // logger.info({ heads, action }, `Document Heads`); + logger.trace({ action, heads }, `DocumentUpdater::Dispatched`); switch (action.type) { case 'Add Components': @@ -355,13 +355,13 @@ export const getDocumentUpdater = (documentId: DocumentId) => { assertNever(action); } - logger.trace({ documentId }, 'get updated doc'); + // logger.trace({ documentId }, 'get updated doc'); latestDocument = await handle.doc(); - logger.trace({ action }, 'retrieved updated doc'); + // logger.trace({ action }, 'retrieved updated doc'); if (latestDocument) { const updatedHeads = getHeads(latestDocument); - logger.info({ action, heads: updatedHeads }, `DocumentUpdater::Exit`); + logger.trace({ action, heads: updatedHeads }, `DocumentUpdater::Exit`); } return latestDocument; }; diff --git a/desci-repo/src/services/nodes.ts b/desci-repo/src/services/nodes.ts index b6d3a3ec8..d7d60236b 100644 --- a/desci-repo/src/services/nodes.ts +++ b/desci-repo/src/services/nodes.ts @@ -4,13 +4,13 @@ import { logger } from '../logger.js'; export const verifyNodeDocumentAccess = async (userId: number, documentId: DocumentId) => { try { - logger.info({ userId, documentId }, 'START [verifyNodeDocumentAccess]::Node'); + logger.trace({ userId, documentId }, 'START [verifyNodeDocumentAccess]::Node'); const rows = await query('SELECT * FROM "Node" WHERE "manifestDocumentId" = $1 AND "ownerId" = $2', [ documentId, userId, ]); const node = rows?.[0]; - logger.info( + logger.trace( { uuid: node.uuid, userId, ownerId: node.ownerId, documentId: node.manifestDocumentId }, '[verifyNodeDocumentAccess]::Node', ); diff --git a/desci-repo/src/services/user.ts b/desci-repo/src/services/user.ts index 7c45358e3..9e72eb931 100644 --- a/desci-repo/src/services/user.ts +++ b/desci-repo/src/services/user.ts @@ -8,21 +8,17 @@ export const hideEmail = (email: string) => { const logger = parentLogger.child({ module: 'UserService' }); export async function getUserByOrcId(orcid: string): Promise { - logger.trace({ fn: 'getUserByOrcId' }, 'user::getUserByOrcId'); - logger.info({ fn: 'getUserByOrcId' }, 'user::getUserByOrcId'); + logger.trace({ orcid }, 'user::getUserByOrcId'); // const user = await prisma.user.findFirst({ where: { orcid } }); const rows = await query('SELECT * FROM "User" WHERE orcid = $1', [orcid]); const user = rows?.[0]; - logger.info({ fn: 'getUserByOrcId' }, 'user::getUserByOrcId'); return user; } export async function getUserByEmail(email: string): Promise { - logger.trace({ fn: 'getUserByEmail' }, `user::getUserByEmail ${hideEmail(email)}`); - logger.info({ email }, 'user::getUserByemail'); - + logger.trace({ email: ` ${hideEmail(email)}` }, `user::getUserByEmail`); const rows = await query('SELECT * FROM "User" WHERE lower(email) = $1', [email.toLowerCase()]); - logger.info({ rowLength: rows?.length }, 'getUserByEmail query'); + logger.trace({ rowLength: rows?.length }, 'getUserByEmail query'); const user = rows?.[0]; diff --git a/desci-server/.eslintrc.cjs b/desci-server/.eslintrc.cjs index 5fa087805..3f766f768 100755 --- a/desci-server/.eslintrc.cjs +++ b/desci-server/.eslintrc.cjs @@ -37,7 +37,7 @@ module.exports = { 'import/resolver': { node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], - moduleDirectory: ['node_modules', 'src/'], + moduleDirectory: ['node_modules', 'src/', '/test'], }, }, }, diff --git a/desci-server/nodemon.json b/desci-server/nodemon.json index f3f391691..af759ce24 100644 --- a/desci-server/nodemon.json +++ b/desci-server/nodemon.json @@ -4,6 +4,6 @@ "ignore": ["log/server.log"], "verbose": true, "exec": "node -r ts-node/register --inspect=0.0.0.0:9228", - "delay": 1000, + "delay": 1337, "signal": "SIGTERM" } diff --git a/desci-server/package.json b/desci-server/package.json index f2a664a6a..2a8b447c3 100755 --- a/desci-server/package.json +++ b/desci-server/package.json @@ -14,7 +14,7 @@ "script:upgrade-manifests": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/upgradeManifests.ts", "script:test-upgrade-manifests": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/testUpgradeManifests.ts", "script:fill-research-fields": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/fill-research-fields.ts", - "script:fix-data-refs": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/dataRefDoctor.ts", + "script:fix-data-refs": "debug=* node --inspect=0.0.0.0:9277 --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/dataRefDoctor.ts", "script:active-users": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/activeUsers.ts", "script:invalidate-redis-cache": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/invalidate-redis-cache.ts", "script:increase-base-drive-storage": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/increase-base-drive-storage.ts", @@ -25,6 +25,7 @@ "script:seed-social-data": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/seed-social-data.ts", "script:DESTRUCTIVE-clear-social-data": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/DESTRUCTIVE-clear-social-data.ts", "script:seed-community-member": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/seed-community-members.ts", + "script:backfill-annotations": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/backfill-annotations.ts", "build": "rimraf dist && tsc && yarn copy-files; if [ \"$SENTRY_AUTH_TOKEN\" ]; then yarn sentry:sourcemaps; else echo 'SENTRY_AUTH_TOKEN not set, sourcemaps will not upload'; fi", "copy-files": "copyfiles -u 1 src/**/*.cjs dist/", "generate": "npx prisma generate", @@ -42,7 +43,6 @@ "test": "yarn test:check && yarn docker:test; export EXIT=$(echo $?); docker compose --file ../docker-compose.test.yml --compatibility down; exit $EXIT", "coverage:destructive": "nyc --all --parser-plugins='[\"importAssertions\"]' -r lcov -e .ts -x \"*.test.ts\" npm run test:destructive", "coverage:destructive:debug": "nyc --all --parser-plugins='[\"importAssertions\"]' -r lcov -e .ts -x \"*.test.ts\" npm run test:destructive:debug", - "commit": "git-cz", "db:forward": "kubectl run --env REMOTE_HOST=$REMOTE_HOST --env REMOTE_PORT=5432 --env LOCAL_PORT=8080 --port 8080 --image marcnuri/port-forward test-port-forward ; kubectl port-forward test-port-forward 8080:8080", "docker:dev": "../dockerDev.sh", "docker:test": "CI=true docker compose --file ../docker-compose.test.yml --compatibility up --exit-code-from nodes_backend_test", @@ -51,7 +51,8 @@ "podman:dev": "podman-compose --file docker-compose.yml --file docker-compose.dev.yml up --build", "email-dev": "email dev --dir ./src/templates/emails --port 3777", "check-deps": "npx npm-check", - "sentry:sourcemaps": "sentry-cli sourcemaps inject --org desci-labs --project nodes-backend ./dist && sentry-cli sourcemaps upload --org desci-labs --project nodes-backend ./dist && echo 'Sentry sourcemaps uploaded'" + "sentry:sourcemaps": "sentry-cli sourcemaps inject --org desci-labs --project nodes-backend ./dist && sentry-cli sourcemaps upload --org desci-labs --project nodes-backend ./dist && echo 'Sentry sourcemaps uploaded'", + "madge": "tsc && npx --yes madge --circular --image circularDepGraph.svg dist/index.js && open circularDepGraph.svg 2>/dev/null || xdg-open circularDepGraph.svg" }, "dependencies": { "@automerge/automerge-repo": "^1.0.19", @@ -59,7 +60,7 @@ "@aws-sdk/client-s3": "^3.537.0", "@desci-labs/desci-codex-lib": "^1.1.7", "@desci-labs/desci-contracts": "^0.2.7", - "@desci-labs/desci-models": "0.2.11", + "@desci-labs/desci-models": "0.2.12", "@elastic/elasticsearch": "^8.14.0", "@honeycombio/opentelemetry-node": "^0.3.2", "@ipld/dag-pb": "^4.0.0", @@ -73,6 +74,7 @@ "@sentry/node": "8.29.0", "@sentry/profiling-node": "8.32.0", "@sentry/tracing": "^7.12.0", + "@socket.io/redis-adapter": "^8.3.0", "@types/lodash-es": "^4.17.12", "@types/mkdirp": "^1.0.2", "@types/multer": "^1.4.7", @@ -110,6 +112,7 @@ "pino": "^8.14.1", "pino-http": "^8.3.3", "pino-pretty": "^10.0.0", + "pino-std-serializers": "^7.0.0", "prisma": "4.10.1", "qs": "^6.12.0", "react-email": "2.1.0", @@ -120,17 +123,16 @@ "rimraf": "^5.0.1", "short-unique-id": "^4.4.4", "siwe": "^1.1.6", + "socket.io": "^4.8.0", "tar": "^6.2.0", "url-safe-base64": "1.2.0", "uuid": "^8.3.2", - "ws": "^8.15.0", + "ws": "^8.18.0", "xml-js": "^1.6.11", "yauzl": "^2.10.0", "zod": "^3.22.4" }, "devDependencies": { - "@commitlint/cli": "^13.1.0", - "@commitlint/config-conventional": "^13.1.0", "@types/archiver": "^6.0.2", "@types/body-parser": "^1.19.1", "@types/chai": "^4.2.21", @@ -142,12 +144,11 @@ "@types/multer-s3": "^3.0.1", "@types/node": "^20.10.4", "@types/supertest": "^2.0.12", - "@types/ws": "^8.5.10", + "@types/ws": "^8.5.12", "@types/xmldom": "^0.1.34", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "chai": "^4.3.4", - "commitizen": "^4.2.4", "copyfiles": "^2.4.1", "dotenv": "^10.0.0", "eslint": "^7.32.0", @@ -173,16 +174,6 @@ "eslint --max-warnings 0" ] }, - "commitlint": { - "extends": [ - "@commitlint/config-conventional" - ] - }, - "config": { - "commitizen": { - "path": "node_modules/cz-conventional-changelog" - } - }, "prisma": { "seed": "node --no-warnings=ExperimentalWarning --loader ts-node/esm prisma/seed.ts" } diff --git a/desci-server/prisma/migrations/20241008193920_add_notifications_table/migration.sql b/desci-server/prisma/migrations/20241008193920_add_notifications_table/migration.sql new file mode 100644 index 000000000..aa69954d1 --- /dev/null +++ b/desci-server/prisma/migrations/20241008193920_add_notifications_table/migration.sql @@ -0,0 +1,27 @@ +-- CreateEnum +CREATE TYPE "NotificationType" AS ENUM ('PUBLISH'); + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "notificationSettings" JSONB; + +-- CreateTable +CREATE TABLE "UserNotifications" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "userId" INTEGER NOT NULL, + "nodeUuid" TEXT, + "type" "NotificationType" NOT NULL, + "title" TEXT NOT NULL, + "message" TEXT NOT NULL, + "payload" JSONB, + "dismissed" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "UserNotifications_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "UserNotifications" ADD CONSTRAINT "UserNotifications_nodeUuid_fkey" FOREIGN KEY ("nodeUuid") REFERENCES "Node"("uuid") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserNotifications" ADD CONSTRAINT "UserNotifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/desci-server/prisma/migrations/20241011115808_add_comments_notif_setting/migration.sql b/desci-server/prisma/migrations/20241011115808_add_comments_notif_setting/migration.sql new file mode 100644 index 000000000..0c500adc1 --- /dev/null +++ b/desci-server/prisma/migrations/20241011115808_add_comments_notif_setting/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "NotificationType" ADD VALUE 'COMMENTS'; diff --git a/desci-server/prisma/schema.prisma b/desci-server/prisma/schema.prisma index 56e55a238..055a9c0c1 100755 --- a/desci-server/prisma/schema.prisma +++ b/desci-server/prisma/schema.prisma @@ -51,6 +51,7 @@ model Node { DoiSubmissionQueue DoiSubmissionQueue[] BookmarkedNode BookmarkedNode[] DeferredEmails DeferredEmails[] + UserNotifications UserNotifications[] Annotation Annotation[] @@index([ownerId]) @@ -154,6 +155,7 @@ model User { isKeeper Boolean @default(false) pseudonym String? @unique orcid String? @unique + notificationSettings Json? // rorPid String[] @default([]) // organization String[] @default([]) isAdmin Boolean @default(false) @@ -199,6 +201,7 @@ model User { OrcidPutCodes OrcidPutCodes[] BookmarkedNode BookmarkedNode[] DeferredEmails DeferredEmails[] + UserNotifications UserNotifications[] @@index([orcid]) @@index([walletAddress]) @@ -911,6 +914,21 @@ model DoiSubmissionQueue { updatedAt DateTime @updatedAt } +model UserNotifications { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + userId Int + nodeUuid String? + type NotificationType + title String + message String + payload Json? // E.g. hyperlinks, DPID, DOI, frontend handles. + dismissed Boolean @default(false) + node Node? @relation(fields: [nodeUuid], references: [uuid]) + user User @relation(fields: [userId], references: [id]) +} + enum ORCIDRecord { WORK QUALIFICATION @@ -1035,3 +1053,8 @@ enum DoiStatus { FAILED SUCCESS } + +enum NotificationType { + PUBLISH + COMMENTS +} diff --git a/desci-server/src/controllers/admin/communities/index.ts b/desci-server/src/controllers/admin/communities/index.ts index 2daba8585..80fb9fa7e 100644 --- a/desci-server/src/controllers/admin/communities/index.ts +++ b/desci-server/src/controllers/admin/communities/index.ts @@ -1,19 +1,13 @@ import { CommunityMembershipRole } from '@prisma/client'; import { NextFunction, Request, Response } from 'express'; +import _ from 'lodash'; import { z } from 'zod'; -import { - asyncMap, - attestationService, - BadRequestError, - communityService, - DuplicateDataError, - NotFoundError, - logger as parentLogger, - prisma, - SuccessMessageResponse, - SuccessResponse, -} from '../../../internal.js'; +import { prisma } from '../../../client.js'; +import { BadRequestError, NotFoundError } from '../../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../../core/ApiResponse.js'; +import { DuplicateDataError } from '../../../core/communities/error.js'; +import { logger as parentLogger } from '../../../logger.js'; import { addAttestationSchema, addCommunitySchema, @@ -23,7 +17,10 @@ import { updateAttestationSchema, updateCommunitySchema, } from '../../../routes/v1/admin/communities/schema.js'; +import { attestationService } from '../../../services/Attestation.js'; +import { communityService } from '../../../services/Communities.js'; import { processUploadToIpfs } from '../../../services/data/processing.js'; +import { asyncMap } from '../../../utils.js'; const logger = parentLogger.child({ module: 'Admin/Communities/controller' }); @@ -61,7 +58,7 @@ export const createCommunity = async (req: Request, res: Response, _next: NextFu } } - const image_url = assets?.find((img) => img.key.toLowerCase().includes('imageurl'))?.url || body.imageUrl; + const image_url = assets?.find((img) => img.key.toLowerCase().includes('image'))?.url || body.imageUrl; delete body.imageUrl; if (!image_url) throw new BadRequestError('No community logo uploaded'); @@ -106,7 +103,7 @@ export const updateCommunity = async (req: Request, res: Response, _next: NextFu // enforce strict non-empty check on image_url field const image_url = - assets?.find((img) => img.key.toLowerCase().includes('imageurl'))?.url || body?.imageUrl || community.image_url; + assets?.find((img) => img.key.toLowerCase().includes('image'))?.url || body?.imageUrl || community.image_url; delete body.imageUrl; if (!image_url) throw new BadRequestError('No community logo uploaded'); @@ -118,7 +115,7 @@ export const updateCommunity = async (req: Request, res: Response, _next: NextFu export const listAllCommunities = async (_req: Request, res: Response, _next: NextFunction) => { const communities = await communityService.adminGetCommunities(); - logger.info({ communities }, 'List communities'); + logger.trace('List communities'); const data = await asyncMap(communities, async (community) => { const engagements = await communityService.getCommunityEngagementSignals(community.id); const verifiedEngagements = await communityService.getCommunityRadarEngagementSignal(community.id); @@ -131,10 +128,83 @@ export const listAllCommunities = async (_req: Request, res: Response, _next: Ne new SuccessResponse(data).send(res); }; +export const listAttestations = async (_req: Request, res: Response, _next: NextFunction) => { + const attestations = await communityService.adminGetAttestations(); + const data = attestations.map((attestation) => ({ + ...attestation, + name: attestation.AttestationVersion[0].name, + image_url: attestation.AttestationVersion[0].image_url, + description: attestation.AttestationVersion[0].description, + })); + // logger.info({ attestations }, 'List attestations'); + new SuccessResponse(data).send(res); +}; + +export const listCommunityAttestations = async ( + req: Request<{ communityId: string }>, + res: Response, + _next: NextFunction, +) => { + const { communityId } = req.params; + const attestations = await communityService.adminGetAttestations({ communityId: parseInt(communityId) }); + // throw new BadRequestError('bad request'); + const entryAttestations = await communityService.getEntryAttestations({ desciCommunityId: parseInt(communityId) }); + const data = attestations + .filter((entry) => !entryAttestations.find((attestation) => attestation.attestationId === entry.id)) + .map((attestation) => ({ + id: attestation.id, + attestationId: attestation.id, + communityId: attestation.communityId, + name: attestation.name, + imageUrl: attestation.AttestationVersion[0].image_url, + description: attestation.description, + protected: attestation.protected, + isRequired: !!attestation.CommunityEntryAttestation.length, + isExternal: false, + communityName: attestation.community.name, + })); + + const entryData = entryAttestations.map((entry) => ({ + id: entry.attestationId, + attestationId: entry.attestationId, + communityId: entry.desciCommunityId, + name: entry.attestationVersion.name, + imageUrl: entry.attestationVersion.image_url, + description: entry.attestationVersion.description, + protected: entry.attestation.protected, + isRequired: true, + entryAttestationId: entry.id, + isExternal: entry.desciCommunityId !== parseInt(communityId), + communityName: entry.attestation.community.name, + })); + + // logger.info({ attestations }, 'List attestations'); + new SuccessResponse(data.concat(entryData)).send(res); +}; + +export const listCommunityEntryAttestations = async ( + req: Request<{ communityId: string }>, + res: Response, + _next: NextFunction, +) => { + const { communityId } = req.params; + const attestations = await communityService.getEntryAttestations({ desciCommunityId: parseInt(communityId) }); + const data = _(attestations) + .map((entry) => ({ + id: entry.id, + attestationId: entry.attestationId, + attestationVersionId: entry.attestationVersion.id, + name: entry.attestationVersion.name, + image_url: entry.attestationVersion.image_url, + })) + .value(); + new SuccessResponse(data).send(res); +}; + export const createAttestation = async (req: Request, res: Response, _next: NextFunction) => { const body = req.body as Required['body']>; const { communityId } = req.params as z.infer['params']; - logger.info({ communityId, body }, 'Payload'); + logger.trace({ communityId, body }, 'Payload'); const community = await communityService.findCommunityById(Number(communityId)); if (!community) throw new NotFoundError(`Community ${communityId} not found`); @@ -146,7 +216,7 @@ export const createAttestation = async (req: Request, res: Response, _next: Next .map((files) => files[0]) .filter(Boolean); - if (uploads) { + if (uploads?.length > 0) { uploads = uploads.map((file) => { file.originalname = `${file.fieldname}.${file.originalname.split('.')?.[1]}`; return file; @@ -163,15 +233,15 @@ export const createAttestation = async (req: Request, res: Response, _next: Next } } - const image_url = assets.find((img) => img.key.toLowerCase().includes('imageurl'))?.url || body.imageUrl; + const image_url = assets?.find((img) => img.key.toLowerCase().includes('image'))?.url || body.imageUrl; delete body.imageUrl; const verified_image_url = - assets.find((img) => img.key.toLowerCase().includes('verifiedimageurl'))?.url || body.verifiedImageUrl; + assets?.find((img) => img.key.toLowerCase().includes('verifiedimage'))?.url || body.verifiedImageUrl; delete body.verifiedImageUrl; - logger.info({ image_url, verified_image_url }, 'Assets'); + logger.trace({ image_url, verified_image_url }, 'Assets'); - if (!image_url) throw new BadRequestError('No community logo uploaded'); + if (!image_url) throw new BadRequestError('No attestation logo uploaded'); const isProtected = body.protected.toString() === 'true' ? true : false; const attestation = await attestationService.create({ @@ -181,6 +251,7 @@ export const createAttestation = async (req: Request, res: Response, _next: Next communityId: community.id, protected: isProtected, }); + // logger.trace({ attestation }, 'created'); new SuccessResponse(attestation).send(res); }; @@ -206,7 +277,7 @@ export const updateAttestation = async (req: Request, res: Response, _next: Next return file; }); const { ok, value } = await processUploadToIpfs({ files: uploads }); - logger.info({ ok, value }, 'Uploads REsult'); + logger.trace({ ok, value }, 'Uploads REsult'); if (ok && value) { assets = value.map((ipfsImg) => ({ key: ipfsImg.path, @@ -217,13 +288,16 @@ export const updateAttestation = async (req: Request, res: Response, _next: Next } } - const image_url = assets?.find((img) => img.key.toLowerCase().includes('imageurl'))?.url || body.imageUrl; + const image_url = + assets?.find((img) => img.key.toLowerCase().includes('image'))?.url || body.imageUrl || exists.image_url; const verified_image_url = - assets?.find((img) => img.key.toLowerCase().includes('verifiedimageurl'))?.url || body.verifiedImageUrl; + assets?.find((img) => img.key.toLowerCase().includes('verifiedimage'))?.url || + body.verifiedImageUrl || + exists.verified_image_url; delete body.imageUrl; delete body.verifiedImageUrl; - logger.info({ image_url, verified_image_url }, 'Assets'); + // logger.info({ image_url, verified_image_url }, 'Assets'); if (!image_url) throw new BadRequestError('No attestation image uploaded'); @@ -289,6 +363,7 @@ export const addEntryAttestation = async (req: Request, res: Response, _next: Ne const data = await attestationService.addCommunityEntryAttestation({ communityId: Number(communityId), attestationId: Number(attestationId), + // set to the lastest version attestationVersion: attestation.AttestationVersion[attestation.AttestationVersion.length - 1].id, }); @@ -301,10 +376,16 @@ export const removeEntryAttestation = async (req: Request, res: Response, _next: const attestation = await attestationService.findAttestationById(+attestationId); if (!attestation) throw new NotFoundError(`No attestation with ID: ${Number(attestationId)} not found!`); + const existing = await attestationService.getCommunityEntryAttestation(Number(communityId), Number(attestationId)); + if (!existing) { + new SuccessMessageResponse().send(res); + return; + } + const data = await attestationService.removeCommunityEntryAttestation({ communityId: Number(communityId), attestationId: Number(attestationId), - attestationVersion: attestation.AttestationVersion[attestation.AttestationVersion.length - 1].id, + attestationVersion: existing.attestationVersionId, }); new SuccessResponse(data).send(res); diff --git a/desci-server/src/controllers/admin/debug.ts b/desci-server/src/controllers/admin/debug.ts index 041904359..652c465fd 100644 --- a/desci-server/src/controllers/admin/debug.ts +++ b/desci-server/src/controllers/admin/debug.ts @@ -1,46 +1,44 @@ +import { DataType, Node, NodeVersion, PublicDataReference } from '@prisma/client'; import { Request, Response } from 'express'; -import { logger as parentLogger } from '../../logger.js'; + import { prisma } from '../../client.js'; -import { DataType, Node, NodeVersion, PublicDataReference } from '@prisma/client'; -import { ensureUuidEndsWithDot } from '../../utils.js'; -import { directStreamLookup } from '../../services/ceramic.js'; +import { logger as parentLogger } from '../../logger.js'; +import { directStreamLookup, RawStream } from '../../services/ceramic.js'; import { getAliasRegistry, getHotWallet } from '../../services/chain.js'; -import { getIndexedResearchObjects, IndexedResearchObject } from '../../theGraph.js'; +import { _getIndexedResearchObjects, getIndexedResearchObjects, IndexedResearchObject } from '../../theGraph.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; const logger = parentLogger.child({ module: 'ADMIN::DebugController' }); -export const debugNodeHandler = async ( - req: Request, - res: Response, -) => { +export const debugNodeHandler = async (req: Request, res: Response) => { const uuid = req.params.uuid; - logger.info({ uuid }, "handling debug query"); + logger.info({ uuid }, 'handling debug query'); const result = await debugNode(uuid); res.send(result); }; type DebugAllNodesQueryParams = { - event?: "first_publish" | "last_publish", - fromDate?: string, - toDate?: string, + event?: 'first_publish' | 'last_publish'; + fromDate?: string; + toDate?: string; }; type NodeInfo = { - uuid: string, - first_publish: Date, - last_publish: Date, + uuid: string; + first_publish: Date; + last_publish: Date; }; /** Be gentle with the search scope, as it'll put a fair amount of load on * the ceramic node -*/ + */ export const debugAllNodesHandler = async ( req: Request, res: Response, ) => { const { fromDate, toDate } = req.query; - const event = req.query.event ?? "last_publish"; + const event = req.query.event ?? 'last_publish'; const startTime = new Date(); @@ -61,37 +59,35 @@ export const debugAllNodesHandler = async ( ${timeClause}; `); - logger.info( - { event, fromDate, toDate, nodes }, - "found nodes matching debug range", - ); + logger.info({ event, fromDate, toDate, nodes }, 'found nodes matching debug range'); const debugData = await Promise.all( - nodes.map(async n => ({ + nodes.map(async (n) => ({ first_publish: n.first_publish, last_publish: n.last_publish, - ...await debugNode(n.uuid) - })) + ...(await debugNode(n.uuid)), + })), ); - const sortedDebugData = debugData.sort((n1, n2) => - // Most recent first - n2[event].getTime() - n1[event].getTime() + const sortedDebugData = debugData.sort( + (n1, n2) => + // Most recent first + n2[event].getTime() - n1[event].getTime(), ); - + const endTime = new Date(); - const duration = Math.round(endTime.getTime() - startTime.getTime()); + const duration = Math.round((endTime.getTime() - startTime.getTime()) / 1000); const result = { info: { startTime, duration: `${duration}s`, event, - fromDate: fromDate ?? "undefined", - toDate: toDate ?? "undefined", + fromDate: fromDate ?? 'undefined', + toDate: toDate ?? 'undefined', }, summary: { - nodesWithErrors: debugData.filter(n => n.hasError).length, + nodesWithErrors: debugData.filter((n) => n.hasError).length, }, data: sortedDebugData, }; @@ -99,62 +95,72 @@ export const debugAllNodesHandler = async ( return res.send(result); }; -const makeTimeClause = ( - event: "first_publish" | "last_publish", - fromDate?: string, - toDate?: string -) => { +const makeTimeClause = (event: 'first_publish' | 'last_publish', fromDate?: string, toDate?: string) => { if (!fromDate && !toDate) { - return ""; + return ''; } let clause = `where all_versions.${event}`; if (fromDate) { clause += ` > '${fromDate}'`; - }; + } if (toDate && fromDate) { clause += `and all_versions.${event} < '${toDate}'`; } else if (toDate) { clause += `< '${toDate}'`; - }; + } return clause; }; -const debugNode = async (uuid: string) => { +type NodeDebugReport = + | { + createdAt: Date; + updatedAt: Date; + hasError: boolean; + nVersionsAgree: boolean; + stream: any; + dpid: any; + database: any; + indexer: any; + migration: any; + } + | { hasError: true; reason: string }; + +const debugNode = async (uuid: string): Promise => { const node: NodeDbClosure = await prisma.node.findFirst({ where: { uuid: ensureUuidEndsWithDot(uuid), }, include: { versions: { - orderBy: { createdAt: "desc" } + orderBy: { createdAt: 'desc' }, }, PublicDataReference: { - orderBy: { createdAt: "desc" } + orderBy: { createdAt: 'desc' }, }, }, }); + if (!node) { + return { + hasError: true, + reason: 'Node not found', + }; + } + const stream = await debugStream(node.ceramicStream); const dpid = await debugDpid(node.dpidAlias); const database = await debugDb(node); const shouldBeIndexed = database.nVersions > 0 || stream.nVersions > 0; const indexer = await debugIndexer(uuid, shouldBeIndexed); + const migration = await debugMigration(uuid, stream); - const nVersionsAgree = new Set([ - database.nVersions ?? 0, - stream.nVersions ?? 0, - indexer.nVersions ?? 0, - ]).size === 1; + const nVersionsAgree = new Set([database.nVersions ?? 0, stream.nVersions ?? 0, indexer.nVersions ?? 0]).size === 1; - const hasError = stream.error - || dpid.error - || database.error - || indexer.error - || !nVersionsAgree; + const hasError = stream.error || dpid.error || database.error || indexer.error || !nVersionsAgree || migration.error; return { createdAt: node.createdAt, @@ -165,43 +171,58 @@ const debugNode = async (uuid: string) => { dpid, database, indexer, + migration, }; }; type NodeDbClosure = Node & { - versions: NodeVersion[], - PublicDataReference: PublicDataReference[], + versions: NodeVersion[]; + PublicDataReference: PublicDataReference[]; +}; + +type DebugStreamResponse = { + present: boolean; + error: boolean; + nVersions?: number; + anchoring?: { + isAnchored: boolean; + timeLeft: number; + }; + raw?: + | RawStream + | { + err: string; + status: number; + body: unknown; + msg: string; + cause: Error; + stack: string; + }; }; /** Get stream state response, or an error object */ -const debugStream = async ( - stream?: string -) => { +const debugStream = async (stream?: string): Promise => { if (!stream) return { present: false, error: false }; const raw = await directStreamLookup(stream); - if ("err" in raw) { + if ('err' in raw) { return { present: true, error: true, raw }; - }; + } - const isAnchored = raw.state.anchorStatus === "ANCHORED"; - const lastCommitIx = raw.state.log.findLastIndex(l => l.expirationTime); + const isAnchored = raw.state.anchorStatus === 'ANCHORED'; + const lastCommitIx = raw.state.log.findLastIndex((l) => l.expirationTime); const lastCommit = raw.state.log[lastCommitIx]; - const tailAnchor = raw.state.log - .slice(lastCommitIx) - .findLast(l => l.type === 2); + const tailAnchor = raw.state.log.slice(lastCommitIx).findLast((l) => l.type === 2); const timeNow = Math.floor(new Date().getTime() / 1000); - const timeLeft = isAnchored - ? lastCommit.expirationTime - tailAnchor.timestamp - : lastCommit.expirationTime - timeNow; + const timeLeft = isAnchored ? lastCommit.expirationTime - tailAnchor.timestamp : lastCommit.expirationTime - timeNow; const anchoring = { isAnchored, - timeLeft + timeLeft, }; // Excluding anchor events - const versions = raw.state.log.filter(l => l.type !== 2); + const versions = raw.state.log.filter((l) => l.type !== 2); return { present: true, @@ -224,53 +245,124 @@ const debugDpid = async (dpid?: number) => { } catch (e) { const err = e as Error; return { present: true, error: true, name: err.name, msg: err.message, stack: err.stack }; - }; + } if (!mappedStream) { return { present: true, error: true, mappedStream: null }; - }; + } return { present: true, error: false, mappedStream }; }; -const debugIndexer = async ( - uuid: string, - shouldExist: boolean -) => { +type DebugMigrationReponse = + | { + error: boolean; + hasLegacyHistory: boolean; + hasStreamHistory: boolean; + ownerMatches?: boolean; + allVersionsPresent?: boolean; + allVersionsOrdered?: boolean; + zipped?: [string, string][]; + } + | { + error: true; + name: string; + message: string; + stack: unknown; + }; + +/* + * Checks if the theres a signature signer mismatch between the legacy contract RO and the stream + */ +const debugMigration = async (uuid?: string, stream?: DebugStreamResponse): Promise => { + // Establish if there is a token history + let legacyHistoryResponse; + try { + legacyHistoryResponse = await _getIndexedResearchObjects([uuid]); + } catch (err) { + logger.error({ uuid, err }, 'Failed to query legacy history'); + return { error: true, name: err.name, message: err.message, stack: err.stack }; + } + + const hasLegacyHistory = !!legacyHistoryResponse?.researchObjects?.length; + + if (!hasLegacyHistory || !stream.present) { + return { error: false, hasLegacyHistory, hasStreamHistory: stream.present }; + } + + const legacyHistory = legacyHistoryResponse.researchObjects[0]; + + let streamHistoryResponse; + try { + streamHistoryResponse = await getIndexedResearchObjects([uuid]); + } catch (err) { + logger.error({ uuid, err }, 'Failed to query stream history'); + return { error: true, name: err.name, message: err.message, stack: err.stack }; + } + + const streamResearchObject = streamHistoryResponse.researchObjects[0]; + const streamController = + stream.present && 'state' in stream.raw ? stream.raw.state.metadata.controllers[0].split(':').pop() : undefined; + const legacyOwner = legacyHistory.owner; + + // Stream Controller === Legacy Contract RO Owner Check + const ownerMatches = streamController?.toLowerCase() === legacyOwner?.toLowerCase(); + + // All Versions Migrated check + const streamManifestCids = streamResearchObject.versions.map((v) => v.cid); + const legacyManifestCids = legacyHistory.versions.map((v) => v.cid); + + const zipped = Array.from( + Array(Math.max(streamManifestCids.length, legacyManifestCids.length)), + (_, i) => [legacyManifestCids[i], streamManifestCids[i]] as [string, string], + ); + + const allVersionsOrdered = zipped.every(([legacyCid, streamCid]) => !legacyCid || legacyCid === streamCid); + const allVersionsPresent = legacyManifestCids.map((cid, i) => streamManifestCids[i] === cid).every(Boolean); + + return { + error: !ownerMatches || !allVersionsPresent || !allVersionsOrdered, + hasLegacyHistory: true, + hasStreamHistory: !!streamController, + ownerMatches, + allVersionsPresent, + allVersionsOrdered, + zipped, + }; +}; + +const debugIndexer = async (uuid: string, shouldExist: boolean) => { let indexResult: { researchObjects: IndexedResearchObject[] }; try { indexResult = await getIndexedResearchObjects([uuid]); } catch (e) { const err = e as Error; return { error: true, name: err.name, msg: err.message, stack: err.stack }; - }; + } const result = indexResult.researchObjects[0]; if (!result) { return { error: shouldExist, result: null }; - }; + } - return { error: false, nVersions: result.versions.length , result }; + return { error: false, nVersions: result.versions.length, result }; }; -const debugDb = async ( - node: NodeDbClosure -) => { +const debugDb = async (node: NodeDbClosure) => { try { - const publishedVersions = node.versions - .filter(nv => nv.transactionId !== null || nv.commitId !== null); + const publishedVersions = node.versions.filter((nv) => nv.transactionId !== null || nv.commitId !== null); const nVersions = publishedVersions.length; - const pubRefManifests = node.PublicDataReference - .filter(pdr => pdr.type == DataType.MANIFEST) - .map(pdr => pdr.cid); + const pubRefManifests = node.PublicDataReference.filter((pdr) => pdr.type == DataType.MANIFEST).map( + (pdr) => pdr.cid, + ); - const publicManifests = publishedVersions.map(v => ({ + const publicManifests = publishedVersions.map((v) => ({ cid: v.manifestUrl, - hasPdr: pubRefManifests.includes(v.manifestUrl) + hasPdr: pubRefManifests.includes(v.manifestUrl), })); - const missingManifestPubRefs = publicManifests.some(m => !m.hasPdr); + const missingManifestPubRefs = publicManifests.some((m) => !m.hasPdr); return { error: missingManifestPubRefs, diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index 03c74e6cd..06f95b281 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -2,21 +2,16 @@ import { ActionType, CommunityEntryAttestation, EmailType } from '@prisma/client import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { - AuthFailureError, - NotFoundError, - SuccessMessageResponse, - SuccessResponse, - asyncMap, - attestationService, - ensureUuidEndsWithDot, - logger, - prisma, -} from '../../internal.js'; +import { prisma } from '../../client.js'; +import { AuthFailureError, NotFoundError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; +import { logger } from '../../logger.js'; import { RequestWithUser } from '../../middleware/authorisation.js'; import { removeClaimSchema } from '../../routes/v1/attestations/schema.js'; +import { attestationService } from '../../services/Attestation.js'; import { saveInteraction } from '../../services/interactionLog.js'; import { getIndexedResearchObjects } from '../../theGraph.js'; +import { asyncMap, ensureUuidEndsWithDot } from '../../utils.js'; export const claimAttestation = async (req: RequestWithUser, res: Response, _next: NextFunction) => { const body = req.body as { diff --git a/desci-server/src/controllers/attestations/comments.ts b/desci-server/src/controllers/attestations/comments.ts index 343bdc472..9d2616784 100644 --- a/desci-server/src/controllers/attestations/comments.ts +++ b/desci-server/src/controllers/attestations/comments.ts @@ -4,22 +4,18 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; import zod from 'zod'; +import { prisma } from '../../client.js'; import { PUBLIC_IPFS_PATH } from '../../config/index.js'; -import { - ForbiddenError, - NotFoundError, - SuccessMessageResponse, - SuccessResponse, - asyncMap, - attestationService, - createCommentSchema, - ensureUuidEndsWithDot, - logger as parentLogger, - prisma, -} from '../../internal.js'; +import { ForbiddenError, NotFoundError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { createCommentSchema } from '../../routes/v1/attestations/schema.js'; +import { attestationService } from '../../services/Attestation.js'; import { saveInteraction } from '../../services/interactionLog.js'; import { client } from '../../services/ipfs.js'; +import { emitNotificationForAnnotation } from '../../services/NotificationService.js'; import { base64ToBlob } from '../../utils/upload.js'; +import { asyncMap, ensureUuidEndsWithDot } from '../../utils.js'; export const getAttestationComments = async (req: Request, res: Response, next: NextFunction) => { const { claimId } = req.params; @@ -129,6 +125,7 @@ export const addComment = async (req: Request, }); } await saveInteraction(req, ActionType.ADD_COMMENT, { annotationId: annotation.id, claimId, authorId }); + await emitNotificationForAnnotation(annotation.id); new SuccessResponse({ ...annotation, highlights: annotation.highlights.map((h) => JSON.parse(h as string)), diff --git a/desci-server/src/controllers/attestations/reactions.ts b/desci-server/src/controllers/attestations/reactions.ts index 16951aa46..b0f8109de 100644 --- a/desci-server/src/controllers/attestations/reactions.ts +++ b/desci-server/src/controllers/attestations/reactions.ts @@ -1,16 +1,10 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { - BadRequestError, - EMOJI_OPTIONS, - ForbiddenError, - NotFoundError, - SuccessMessageResponse, - SuccessResponse, - attestationService, - logger as parentLogger, -} from '../../internal.js'; +import { ForbiddenError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; export const getAttestationReactions = async (req: Request, res: Response, next: NextFunction) => { const logger = parentLogger.child({ diff --git a/desci-server/src/controllers/attestations/recommendations.ts b/desci-server/src/controllers/attestations/recommendations.ts index cea1b0d66..42d9bbca6 100644 --- a/desci-server/src/controllers/attestations/recommendations.ts +++ b/desci-server/src/controllers/attestations/recommendations.ts @@ -1,13 +1,11 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { - NotFoundError, - SuccessResponse, - attestationService, - communityService, - logger as parentLogger, -} from '../../internal.js'; +import { NotFoundError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; +import { communityService } from '../../services/Communities.js'; const logger = parentLogger.child({ module: 'Recommendations' }); diff --git a/desci-server/src/controllers/attestations/show.ts b/desci-server/src/controllers/attestations/show.ts index 118666b31..d11e0c9a6 100644 --- a/desci-server/src/controllers/attestations/show.ts +++ b/desci-server/src/controllers/attestations/show.ts @@ -1,8 +1,10 @@ import { AttestationVersion, DesciCommunity, NodeAttestation } from '@prisma/client'; import { Request, Response } from 'express'; -import { BadRequestError, SuccessResponse, attestationService } from '../../internal.js'; +import { BadRequestError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; export type NodeAttestationFragment = NodeAttestation & { community: Pick; diff --git a/desci-server/src/controllers/attestations/verification.ts b/desci-server/src/controllers/attestations/verification.ts index 48ce36b2e..c69359e63 100644 --- a/desci-server/src/controllers/attestations/verification.ts +++ b/desci-server/src/controllers/attestations/verification.ts @@ -3,17 +3,14 @@ import { NextFunction, Request, Response } from 'express'; // import { Attestation, NodeAttestation } from '@prisma/client'; import _ from 'lodash'; -import { - ForbiddenError, - SuccessMessageResponse, - SuccessResponse, - attestationService, - ensureUuidEndsWithDot, - prisma, -} from '../../internal.js'; +import { prisma } from '../../client.js'; +import { ForbiddenError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; import { saveInteraction, saveInteractionWithoutReq } from '../../services/interactionLog.js'; import orcidApiService from '../../services/orcid.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; type RemoveVerificationBody = { verificationId: string; diff --git a/desci-server/src/controllers/auth/orcidNext.ts b/desci-server/src/controllers/auth/orcidNext.ts index bb04093f5..44f30b4d7 100644 --- a/desci-server/src/controllers/auth/orcidNext.ts +++ b/desci-server/src/controllers/auth/orcidNext.ts @@ -21,6 +21,18 @@ export const orcidCheck = async (req: Request, res: Response) => { logger.trace({ fn: 'orcid check', body: req.body }); if (!req.body || !req.body.orcid || !req.body.access_token || !req.body.refresh_token || !req.body.expires_in) { + logger.error( + { + fn: 'orcid check', + step: 2, + bodyMissing: !req.body, + orcidMissing: !req.body.orcid, + accessTokenMissing: !req.body.access_token, + refreshTokenMissing: !req.body.refresh_token, + expiresInMissing: !req.body.expires_in, + }, + 'missing orcid data', + ); res.status(400).send({ err: 'missing orcid data', code: 0 }); return; } diff --git a/desci-server/src/controllers/auth/profile.ts b/desci-server/src/controllers/auth/profile.ts index 537b91205..01508ee4c 100755 --- a/desci-server/src/controllers/auth/profile.ts +++ b/desci-server/src/controllers/auth/profile.ts @@ -23,6 +23,7 @@ export const profile = async (req: Request, res: Response, next: NextFunction) = orcid: user.orcid, userOrganization: organization.map((org) => org.organization), consent: !!(await getUserConsent(user.id)), + notificationSettings: user.notificationSettings || {}, }, }; try { diff --git a/desci-server/src/controllers/communities/feed.ts b/desci-server/src/controllers/communities/feed.ts index 875df854b..eb73327e2 100644 --- a/desci-server/src/controllers/communities/feed.ts +++ b/desci-server/src/controllers/communities/feed.ts @@ -1,15 +1,15 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { - NotFoundError, - SuccessResponse, - asyncMap, - attestationService, - communityService, - resolveLatestNode, -} from '../../internal.js'; -import { logger as parentLogger } from '../../internal.js'; +import { NotFoundError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; +import { communityService } from '../../services/Communities.js'; +import { asyncMap } from '../../utils.js'; + +import { resolveLatestNode } from './util.js'; + const logger = parentLogger.child({ module: 'communities/feed.ts' }); export const getCommunityFeed = async (req: Request, res: Response, next: NextFunction) => { diff --git a/desci-server/src/controllers/communities/guard.ts b/desci-server/src/controllers/communities/guard.ts index d788627c8..c611f0402 100644 --- a/desci-server/src/controllers/communities/guard.ts +++ b/desci-server/src/controllers/communities/guard.ts @@ -1,7 +1,9 @@ import { Response } from 'express'; -import { SuccessResponse, communityService, logger } from '../../internal.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger } from '../../logger.js'; import { RequestWithUser } from '../../middleware/authorisation.js'; +import { communityService } from '../../services/Communities.js'; export const checkMemberGuard = async (req: RequestWithUser, res: Response) => { const log = logger.child({ diff --git a/desci-server/src/controllers/communities/list.ts b/desci-server/src/controllers/communities/list.ts index 8420bb01f..5193f6e75 100644 --- a/desci-server/src/controllers/communities/list.ts +++ b/desci-server/src/controllers/communities/list.ts @@ -1,8 +1,10 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { SuccessResponse, communityService } from '../../internal.js'; -import { logger as parentLogger, asyncMap } from '../../internal.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { communityService } from '../../services/Communities.js'; +import { asyncMap } from '../../utils.js'; const logger = parentLogger.child({ module: 'LIST COMMUNITIES' }); diff --git a/desci-server/src/controllers/communities/radar.ts b/desci-server/src/controllers/communities/radar.ts index 0c3135efe..4d66a6ec0 100644 --- a/desci-server/src/controllers/communities/radar.ts +++ b/desci-server/src/controllers/communities/radar.ts @@ -2,14 +2,13 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { - SuccessResponse, - asyncMap, - attestationService, - communityService, - logger as parentLogger, - resolveLatestNode, -} from '../../internal.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { attestationService } from '../../services/Attestation.js'; +import { communityService } from '../../services/Communities.js'; +import { asyncMap } from '../../utils.js'; + +import { resolveLatestNode } from './util.js'; const logger = parentLogger.child({ module: 'GET COMMUNITY RADAR' }); export const getCommunityRadar = async (req: Request, res: Response, next: NextFunction) => { diff --git a/desci-server/src/controllers/communities/types.ts b/desci-server/src/controllers/communities/types.ts index 534e9c6cb..b66657b9b 100644 --- a/desci-server/src/controllers/communities/types.ts +++ b/desci-server/src/controllers/communities/types.ts @@ -1,7 +1,7 @@ import { ResearchObjectV1 } from '@desci-labs/desci-models'; import { Node } from '@prisma/client'; -import { CommunityRadarNode } from '../../internal.js'; +import { CommunityRadarNode } from '../../services/Communities.js'; export type NodeRadarItem = { NodeAttestation: CommunityRadarNode[]; diff --git a/desci-server/src/controllers/communities/util.ts b/desci-server/src/controllers/communities/util.ts index 469be6ba7..c5269399f 100644 --- a/desci-server/src/controllers/communities/util.ts +++ b/desci-server/src/controllers/communities/util.ts @@ -2,10 +2,15 @@ import { Node, NodeVersion } from '@prisma/client'; import axios from 'axios'; import _ from 'lodash'; -import { NodeRadar, ensureUuidEndsWithDot } from '../../internal.js'; -import { NodeUuid, cleanupManifestUrl, logger, prisma } from '../../internal.js'; +import { prisma } from '../../client.js'; +import { logger } from '../../logger.js'; +import { NodeUuid } from '../../services/manifestRepo.js'; import repoService from '../../services/repoService.js'; import { IndexedResearchObject, getIndexedResearchObjects } from '../../theGraph.js'; +import { cleanupManifestUrl } from '../../utils/manifest.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; + +import { NodeRadar } from './types.js'; export const resolveLatestNode = async (radar: Partial) => { const uuid = ensureUuidEndsWithDot(radar.nodeuuid); diff --git a/desci-server/src/controllers/data/retrieve.ts b/desci-server/src/controllers/data/retrieve.ts index 245fb6816..142206438 100644 --- a/desci-server/src/controllers/data/retrieve.ts +++ b/desci-server/src/controllers/data/retrieve.ts @@ -10,10 +10,10 @@ import tar from 'tar'; import { prisma } from '../../client.js'; import { logger as parentLogger } from '../../logger.js'; -import redisClient, { getOrCache } from '../../redisClient.js'; +import { redisClient, getOrCache } from '../../redisClient.js'; import { getLatestDriveTime } from '../../services/draftTrees.js'; import { getDatasetTar } from '../../services/ipfs.js'; -import { NodeUuid, getLatestManifestFromNode } from '../../services/manifestRepo.js'; +import { NodeUuid } from '../../services/manifestRepo.js'; import { showNodeDraftManifest } from '../../services/nodeManager.js'; import { getTreeAndFill, getTreeAndFillDeprecated } from '../../utils/driveUtils.js'; import { cleanupManifestUrl } from '../../utils/manifest.js'; diff --git a/desci-server/src/controllers/doi/admin.ts b/desci-server/src/controllers/doi/admin.ts index d19a3d370..0efa68e6e 100644 --- a/desci-server/src/controllers/doi/admin.ts +++ b/desci-server/src/controllers/doi/admin.ts @@ -1,6 +1,9 @@ import { NextFunction, Response } from 'express'; -import { RequestWithUser, SuccessResponse, doiService, logger as parentLogger } from '../../internal.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { RequestWithUser } from '../../middleware/authorisation.js'; +import { doiService } from '../../services/index.js'; const logger = parentLogger.child({ module: 'ADMIN::DOI' }); export const listDoiRecords = async (_req: RequestWithUser, res: Response, _next: NextFunction) => { diff --git a/desci-server/src/controllers/doi/check.ts b/desci-server/src/controllers/doi/check.ts index 383d8684a..1c3968950 100644 --- a/desci-server/src/controllers/doi/check.ts +++ b/desci-server/src/controllers/doi/check.ts @@ -1,17 +1,13 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; -import { DoiError } from '../../core/doi/error.js'; -import { - BadRequestError, - RequestWithNode, - SuccessResponse, - doiService, - ensureUuidEndsWithDot, - logger as parentLogger, -} from '../../internal.js'; +import { ApiError, BadRequestError, ForbiddenError, InternalError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { DoiError, ForbiddenMintError } from '../../core/doi/error.js'; +import { logger as parentLogger } from '../../logger.js'; +import { RequestWithNode } from '../../middleware/authorisation.js'; import { OpenAlexWork, transformInvertedAbstractToText } from '../../services/AutomatedMetadata.js'; - +import { doiService } from '../../services/index.js'; const pg = await import('pg').then((value) => value.default); const { Client } = pg; @@ -29,11 +25,9 @@ export const checkMintability = async (req: RequestWithNode, res: Response, _nex await doiService.checkMintability(uuid); new SuccessResponse(true).send(res); } catch (err) { - logger.error(err, 'module:: checkMintability'); - if (!(err instanceof DoiError)) { - // TODO: Sentry error reporting - } new SuccessResponse(false).send(res); + const error = err as DoiError; + logger.trace({ error, uuid }, 'checkMintabilityError'); } }; @@ -71,28 +65,30 @@ export const retrieveDoi = async (req: Request, res: Response, _next: NextFuncti // pull record from openalex database const { rows } = await client.query( - `select pdf_url, - landing_page_url, + `select + COALESCE(wol.pdf_url, '') as pdf_url, + COALESCE(wol.landing_page_url, '') as landing_page_url, works.title as title, works.id as works_id, works."type" as work_type, works.publication_year, works.cited_by_count as citation_count, - woa.oa_status, - source.publisher, - source.display_name as source_name, + COALESCE(woa.oa_status, 'unknown') as oa_status, + COALESCE(source.publisher, 'unknown') as publisher, + COALESCE(source.display_name, 'unknown') as source_name, ARRAY( - SELECT author.display_name as author_name FROM openalex.works_authorships wauth - left join openalex.authors author on author.id = wauth.author_id + SELECT author.display_name as author_name + FROM openalex.works_authorships wauth + LEFT JOIN openalex.authors author on author.id = wauth.author_id WHERE wauth.work_id = works.id ) as authors - from openalex.works_best_oa_locations wol - left join openalex.works works on works.id = wol.work_id - left JOIN openalex.works_authorships wa on works.id = wa.work_id - left JOIN openalex.works_open_access woa on woa.work_id = works.id - left JOIN openalex.sources source on source.id = wol.source_id - where works.doi = $1 - GROUP BY wol.pdf_url, landing_page_url,title, works_id, work_type, citation_count, works.publication_year, woa.oa_status, source.publisher, source_name;`, +from openalex.works works +left join openalex.works_best_oa_locations wol on works.id = wol.work_id +left join openalex.works_authorships wa on works.id = wa.work_id +left join openalex.works_open_access woa on woa.work_id = works.id +left join openalex.sources source on source.id = wol.source_id +where works.doi = $1 +group by wol.pdf_url, wol.landing_page_url, works.title, works.id, works."type", works.cited_by_count, works.publication_year, woa.oa_status, source.publisher, source.display_name;`, [doiLink], ); diff --git a/desci-server/src/controllers/doi/mint.ts b/desci-server/src/controllers/doi/mint.ts index 1072035f9..25b1f888f 100644 --- a/desci-server/src/controllers/doi/mint.ts +++ b/desci-server/src/controllers/doi/mint.ts @@ -3,21 +3,16 @@ import sgMail from '@sendgrid/mail'; import { Request, Response, NextFunction } from 'express'; import _ from 'lodash'; +import { prisma } from '../../client.js'; +import { BadRequestError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; import { MintError } from '../../core/doi/error.js'; -import { - BadRequestError, - NotFoundError, - SuccessMessageResponse, - SuccessResponse, - crossRefClient, - doiService, - ensureUuidEndsWithDot, - logger as parentLogger, - prisma, -} from '../../internal.js'; +import { logger as parentLogger } from '../../logger.js'; import { getTargetDpidUrl } from '../../services/fixDpid.js'; +import { crossRefClient, doiService } from '../../services/index.js'; import { DoiMintedEmailHtml } from '../../templates/emails/utils/emailRenderer.js'; -import { discordNotify, DiscordNotifyType } from '../../utils/discordUtils.js'; +import { DiscordChannel, discordNotify, DiscordNotifyType } from '../../utils/discordUtils.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; export const mintDoi = async (req: Request, res: Response, _next: NextFunction) => { const { uuid } = req.params; @@ -33,6 +28,7 @@ export const mintDoi = async (req: Request, res: Response, _next: NextFunction) const targetDpidUrl = getTargetDpidUrl(); discordNotify({ + channel: DiscordChannel.DoiMinting, type: DiscordNotifyType.INFO, title: 'Mint DOI', message: `${targetDpidUrl}/${submission.dpid} sent a request to mint: ${submission.uniqueDoi}`, @@ -68,10 +64,11 @@ export const handleCrossrefNotificationCallback = async ( return; } - if (submission.status === DoiStatus.SUCCESS) { - new SuccessMessageResponse().send(res); - return; - } + // if (submission.status === DoiStatus.SUCCESS) { + // logger.trace({ payload: req.payload }, 'Crossref Notifiication: submission '); + // new SuccessMessageResponse().send(res); + // return; + // } await doiService.updateSubmission({ id: submission.id }, { notification: req.payload }); logger.info('SUBMISSION UPDATED'); @@ -92,6 +89,7 @@ export const handleCrossrefNotificationCallback = async ( // send discord notification discordNotify({ + channel: DiscordChannel.DoiMinting, type: DiscordNotifyType.SUCCESS, title: 'DOI Registration successful 🎉', message: `${targetDpidUrl}/${submission.dpid} was assigned a DOI: ${submission.uniqueDoi}`, @@ -113,7 +111,8 @@ export const handleCrossrefNotificationCallback = async ( dpid: submission.dpid, userName: node.owner.name.split(' ')?.[0] ?? '', dpidPath: `${process.env.DAPP_URL}/dpid/${submission.dpid}`, - doi: `${process.env.CROSSREF_DOI_URL}/${submission.uniqueDoi}`, + doi: submission.uniqueDoi, + doiLink: `${process.env.CROSSREF_DOI_URL}/${submission.uniqueDoi}`, nodeTitle: node.title, }), }; @@ -138,7 +137,8 @@ export const handleCrossrefNotificationCallback = async ( // send discord notification discordNotify({ - type: DiscordNotifyType.SUCCESS, + channel: DiscordChannel.DoiMinting, + type: DiscordNotifyType.ERROR, title: 'DOI Registration Inconclusive ❌', message: `Check ${req.payload.retrieveUrl} for more details. Node: ${targetDpidUrl}/${submission.dpid}`, }); diff --git a/desci-server/src/controllers/log/userAction.ts b/desci-server/src/controllers/log/userAction.ts index 4d67dd6e2..569f9be54 100644 --- a/desci-server/src/controllers/log/userAction.ts +++ b/desci-server/src/controllers/log/userAction.ts @@ -1,68 +1,10 @@ +import { AvailableUserActionLogTypes } from '@desci-labs/desci-models'; import { ActionType, User } from '@prisma/client'; import { Request, Response, NextFunction } from 'express'; import { logger } from '../../logger.js'; import { saveInteraction } from '../../services/interactionLog.js'; -/** - * TODO: Put this in desci-models? - */ -export enum AvailableUserActionLogTypes { - publishStep = 'publishStep', - btnPublishActivityBar = 'btnPublishActivityBar', - btnSidebarNavigation = 'btnSidebarNavigation', - tabProfilePublishedNodes = 'tabProfilePublishedNodes', - tabProfileSharedNodes = 'tabProfileSharedNodes', - tabProfileAllNodes = 'tabProfileAllNodes', - btnProfileCreateNewResearchObject = 'btnProfileCreateNewResearchObject', - btnProfileCreateNewSubmissionPackage = 'btnProfileCreateNewSubmissionPackage', - btnDownloadData = 'btnDownloadData', - btnDownloadManuscript = 'btnDownloadManuscript', - btnShare = 'btnShare', - btnPublish = 'btnPublish', - btnAddComponentFab = 'btnAddComponentFab', - btnAddComponentDrive = 'btnAddComponentDrive', - btnAddComponentDriveNewComponent = 'btnAddComponentDriveNewComponent', - btnAddComponentDriveNewFolder = 'btnAddComponentDriveNewFolder', - driveNavigateBreadcrumb = 'driveNavigateBreadcrumb', - btnFigureAnnotate = 'btnFigureAnnotate', - btnContinuePublish = 'btnContinuePublish', - btnReviewBeforePublish = 'btnReviewBeforePublish', - dismissCommitAdditionalInfo = 'dismissCommitAdditionalInfo', - dismissCommitStatus = 'dismissCommitStatus', - completePublish = 'completePublish', - btnSignPublish = 'btnSignPublish', - commitPanelDismiss = 'commitPanelDismiss', - viewWalletSettings = 'viewWalletSettings', - walletMoreOptions = 'walletMoreOptions', - walletSwitchChain = 'walletSwitchChain', - walletClickCard = 'walletClickCard', - walletError = 'walletError', - walletDisconnect = 'walletDisconnect', - connectWallet = 'connectWallet', - btnComponentCardCite = 'btnComponentCardCite', - btnComponentCardViewFile = 'btnComponentCardViewFile', - btnComponentCardUse = 'btnComponentCardUse', - btnComponentCardViewLink = 'btnComponentCardViewLink', - btnComponentCardViewMetadata = 'btnComponentCardViewMetadata', - viewDrive = 'viewDrive', - btnDriveCite = 'btnDriveCite', - btnDriveUse = 'btnDriveUse', - btnDriveStarToggle = 'btnDriveStarToggle', - saveMetadata = 'saveMetadata', - btnInspectMetadata = 'btnInspectMetadata', - ctxDriveRename = 'ctxDriveRename', - ctxDrivePreview = 'ctxDrivePreview', - ctxDriveDownload = 'ctxDriveDownload', - ctxDriveDelete = 'ctxDriveDelete', - ctxDriveAssignType = 'ctxDriveAssignType', - ctxDriveEditMetadata = 'ctxDriveEditMetadata', - btnCreateNewNode = 'btnCreateNewNode', - btnCreateNodeModalSave = 'btnCreateNodeModalSave', - errNodeCreate = 'errNodeCreate', - viewedNode = 'viewedNode', -} - /** * Note: user not guaranteed */ diff --git a/desci-server/src/controllers/nodes/bookmarks/create.ts b/desci-server/src/controllers/nodes/bookmarks/create.ts index 11a5c14a7..54ff5f5c9 100644 --- a/desci-server/src/controllers/nodes/bookmarks/create.ts +++ b/desci-server/src/controllers/nodes/bookmarks/create.ts @@ -2,8 +2,8 @@ import { User } from '@prisma/client'; import { Request, Response } from 'express'; import { prisma } from '../../../client.js'; -import { ensureUuidEndsWithDot } from '../../../internal.js'; import { logger as parentLogger } from '../../../logger.js'; +import { ensureUuidEndsWithDot } from '../../../utils.js'; export type CreateNodeBookmarkReqBody = { nodeUuid: string; diff --git a/desci-server/src/controllers/nodes/bookmarks/delete.ts b/desci-server/src/controllers/nodes/bookmarks/delete.ts index 9d2bd3d33..af0264086 100644 --- a/desci-server/src/controllers/nodes/bookmarks/delete.ts +++ b/desci-server/src/controllers/nodes/bookmarks/delete.ts @@ -2,8 +2,8 @@ import { User } from '@prisma/client'; import { Request, Response } from 'express'; import { prisma } from '../../../client.js'; -import { ensureUuidEndsWithDot } from '../../../internal.js'; import { logger as parentLogger } from '../../../logger.js'; +import { ensureUuidEndsWithDot } from '../../../utils.js'; export type DeleteNodeBookmarkRequest = Request<{ nodeUuid: string }, never> & { user: User; // added by auth middleware diff --git a/desci-server/src/controllers/nodes/bookmarks/index.ts b/desci-server/src/controllers/nodes/bookmarks/index.ts index 5eab47349..ec45633d8 100644 --- a/desci-server/src/controllers/nodes/bookmarks/index.ts +++ b/desci-server/src/controllers/nodes/bookmarks/index.ts @@ -2,8 +2,8 @@ import { User } from '@prisma/client'; import { Request, Response } from 'express'; import { prisma } from '../../../client.js'; -import { getLatestManifestFromNode } from '../../../internal.js'; import { logger as parentLogger } from '../../../logger.js'; +import { getLatestManifestFromNode } from '../../../services/manifestRepo.js'; export type BookmarkedNode = { uuid: string; diff --git a/desci-server/src/controllers/nodes/checkIfPublishedNode.ts b/desci-server/src/controllers/nodes/checkIfPublishedNode.ts index ec9de2881..854927661 100644 --- a/desci-server/src/controllers/nodes/checkIfPublishedNode.ts +++ b/desci-server/src/controllers/nodes/checkIfPublishedNode.ts @@ -1,12 +1,12 @@ +import { ResearchObjectV1, ResearchObjectV1Dpid } from '@desci-labs/desci-models'; +import { NodeCover } from '@prisma/client'; import type { Request, Response, NextFunction } from 'express'; import { prisma } from '../../client.js'; -import { resolveNodeManifest } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; -import { decodeBase64UrlSafeToHex, ensureUuidEndsWithDot, randomUUID64 } from '../../utils.js'; import { IndexedResearchObject, getIndexedResearchObjects } from '../../theGraph.js'; -import { ResearchObjectV1, ResearchObjectV1Dpid } from '@desci-labs/desci-models'; -import { NodeCover } from '@prisma/client'; +import { resolveNodeManifest } from '../../utils/manifest.js'; +import { decodeBase64UrlSafeToHex, ensureUuidEndsWithDot, randomUUID64 } from '../../utils.js'; const logger = parentLogger.child({ module: 'NODE::checkIfPublishedNode', @@ -49,7 +49,7 @@ export const checkIfPublishedNode = async ( ipfsQuery, }); - let node = await prisma.node.findFirst({ + const node = await prisma.node.findFirst({ select: { uuid: true, id: true, diff --git a/desci-server/src/controllers/nodes/comments.ts b/desci-server/src/controllers/nodes/comments.ts index d9149b882..7e19275db 100644 --- a/desci-server/src/controllers/nodes/comments.ts +++ b/desci-server/src/controllers/nodes/comments.ts @@ -2,16 +2,14 @@ import { Response, NextFunction } from 'express'; import _ from 'lodash'; import z from 'zod'; -import { - NotFoundError, - RequestWithNode, - SuccessResponse, - attestationService, - ensureUuidEndsWithDot, - getCommentsSchema, - logger, - prisma, -} from '../../internal.js'; +import { prisma } from '../../client.js'; +import { NotFoundError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger } from '../../logger.js'; +import { RequestWithNode } from '../../middleware/authorisation.js'; +import { getCommentsSchema } from '../../routes/v1/attestations/schema.js'; +import { attestationService } from '../../services/Attestation.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; export const getGeneralComments = async (req: RequestWithNode, res: Response, _next: NextFunction) => { const { uuid } = req.params as z.infer['params']; diff --git a/desci-server/src/controllers/nodes/consent.ts b/desci-server/src/controllers/nodes/consent.ts index 835d80aca..e874e125c 100644 --- a/desci-server/src/controllers/nodes/consent.ts +++ b/desci-server/src/controllers/nodes/consent.ts @@ -3,8 +3,10 @@ import { Request, Response, NextFunction } from 'express'; import _ from 'lodash'; import { z as zod } from 'zod'; -import { SuccessMessageResponse, SuccessResponse, ensureUuidEndsWithDot, logger } from '../../internal.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; +import { logger } from '../../logger.js'; import { getUserConsent, getUserPublishConsent, saveInteraction } from '../../services/interactionLog.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; export const consent = async (req: Request, res: Response, next: NextFunction) => { const user = (req as any).user; diff --git a/desci-server/src/controllers/nodes/doi.ts b/desci-server/src/controllers/nodes/doi.ts index a309d06ee..2a03e1643 100644 --- a/desci-server/src/controllers/nodes/doi.ts +++ b/desci-server/src/controllers/nodes/doi.ts @@ -12,24 +12,18 @@ import { Request } from 'express'; import _ from 'lodash'; import { z } from 'zod'; -import { - BadRequestError, - NotFoundError, - RequestWithNode, - SuccessResponse, - UnProcessableRequestError, - crossRefClient, - doiService, - ensureUuidEndsWithDot, - getLatestManifestFromNode, - logger, - metadataClient, - logger as parentLogger, -} from '../../internal.js'; +import { BadRequestError, NotFoundError, UnProcessableRequestError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; +import { logger as parentLogger } from '../../logger.js'; +import { logger } from '../../logger.js'; +import { RequestWithNode } from '../../middleware/authorisation.js'; import { MetadataResponse } from '../../services/AutomatedMetadata.js'; import { Work, WorkSelectOptions } from '../../services/crossRef/definitions.js'; import { getOrcidFromURL } from '../../services/crossRef/utils.js'; +import { crossRefClient, doiService, metadataClient } from '../../services/index.js'; +import { getLatestManifestFromNode } from '../../services/manifestRepo.js'; import repoService from '../../services/repoService.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; export const attachDoiSchema = z.object({ body: z.object({ diff --git a/desci-server/src/controllers/nodes/draftCreate.ts b/desci-server/src/controllers/nodes/draftCreate.ts index 4b99df8b5..3d33ee528 100755 --- a/desci-server/src/controllers/nodes/draftCreate.ts +++ b/desci-server/src/controllers/nodes/draftCreate.ts @@ -146,7 +146,8 @@ export const draftCreate = async (req: Request, res: Response, next: NextFunctio }); // cache initial doc for a minute (60) - await setToCache(`node-draft-${node.uuid}`, { document, documentId }, 60); + // ! disabling, as it breaks programmatic interaction from nodes-lib, where stale results break interactivity + // await setToCache(`node-draft-${ensureUuidEndsWithDot(node.uuid)}`, { document, documentId }, 60); return; } catch (err) { diff --git a/desci-server/src/controllers/nodes/getPublishedNodes.ts b/desci-server/src/controllers/nodes/getPublishedNodes.ts index 293e0dabc..16ad1605e 100644 --- a/desci-server/src/controllers/nodes/getPublishedNodes.ts +++ b/desci-server/src/controllers/nodes/getPublishedNodes.ts @@ -1,8 +1,8 @@ import { User } from '@prisma/client'; import { Request, Response } from 'express'; -import { cachedGetDpidFromManifest } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; +import { cachedGetDpidFromManifest } from '../../utils/manifest.js'; import { asyncMap } from '../../utils.js'; import { listAllUserNodes, PublishedNode } from './list.js'; diff --git a/desci-server/src/controllers/nodes/list.ts b/desci-server/src/controllers/nodes/list.ts index b6b488911..7fe586184 100755 --- a/desci-server/src/controllers/nodes/list.ts +++ b/desci-server/src/controllers/nodes/list.ts @@ -1,9 +1,10 @@ +import { Prisma, PrismaClient, User } from '@prisma/client'; import { Request, Response } from 'express'; + import { prisma } from '../../client.js'; -import { cachedGetDpidFromManifest } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; +import { cachedGetDpidFromManifest } from '../../utils/manifest.js'; import { asyncMap, randomUUID64 } from '../../utils.js'; -import { Prisma, PrismaClient, User } from '@prisma/client'; const logger = parentLogger.child({ module: 'NODE::listController', @@ -17,12 +18,9 @@ type UserNodesQueryParams = { }; // User populated by auth middleware -type UserNodesRequest = - Request & { user: User }; +type UserNodesRequest = Request & { user: User }; -export type UserNode = - | PublishedNode - | DraftNode; +export type UserNode = PublishedNode | DraftNode; export type PublishedNode = { uuid: string; @@ -54,20 +52,20 @@ type UserNodesResponse = Response<{ nodes: UserNode[]; }>; -export const list = async ( - req: UserNodesRequest, - res: UserNodesResponse, -) => { +export const list = async (req: UserNodesRequest, res: UserNodesResponse) => { const owner = req.user; const gateway = req.query.g; const page: number = req.query.page ? parseInt(req.query.page as string) : 1; const size: number = req.query.size ? parseInt(req.query.size as string) : 10; - logger.info({ - queryParams: req.query, - gateway, - }, "getting all user nodes"); + logger.info( + { + queryParams: req.query, + gateway, + }, + 'getting all user nodes', + ); let nodes = await listAllUserNodes(owner.id, page, size); @@ -88,14 +86,14 @@ export const list = async ( ), ); nodes = await listAllUserNodes(owner.id, page, size); - }; + } - const formattedNodes = await asyncMap(nodes, async n => { - logger.info({ uuid: n.uuid, versions: n.versions }) + const formattedNodes = await asyncMap(nodes, async (n) => { + logger.info({ uuid: n.uuid, versions: n.versions }); const isPublished = n.versions.length > 0; const draftInfo = { - uuid: n.uuid.replace(".", ""), + uuid: n.uuid.replace('.', ''), title: n.title, createdAt: n.createdAt, }; @@ -104,70 +102,60 @@ export const list = async ( const cid = n.versions[0].manifestUrl; return { ...draftInfo, - dpid: n.dpidAlias ?? await cachedGetDpidFromManifest(cid, gateway), + dpid: n.dpidAlias ?? (await cachedGetDpidFromManifest(cid, gateway)), versionIx: n.versions.length - 1, publishedAt: n.versions[0].createdAt, isPublished: true as const, - } + }; } else { return { ...draftInfo, isPublished: false as const, }; - }; + } }); res.send({ nodes: formattedNodes }); }; /** List all nodes for the given user, including published versions, if any */ -export const listAllUserNodes = async ( - ownerId: number, - page: number, - size: number, - onlyPublished = false, -) => await prisma.node.findMany({ - select: { - id: true, - uuid: true, - title: true, - createdAt: true, - dpidAlias: true, - versions: { - select: { - manifestUrl: true, - createdAt: true, - commitId: true, - transactionId: true, - }, - where: { - OR: [ - { transactionId: { not: null } }, - { commitId: { not: null } }, - ] +export const listAllUserNodes = async (ownerId: number, page: number, size: number, onlyPublished = false) => + await prisma.node.findMany({ + select: { + id: true, + uuid: true, + title: true, + createdAt: true, + dpidAlias: true, + versions: { + select: { + manifestUrl: true, + createdAt: true, + commitId: true, + transactionId: true, + }, + where: { + OR: [{ transactionId: { not: null } }, { commitId: { not: null } }], + }, + orderBy: { createdAt: 'desc' }, }, - orderBy: { createdAt: "desc" } - } - }, - where: { - ownerId, - isDeleted: false, - // Can't filter afterward with onlyPublished because then paging won't make sense - ...(onlyPublished ? onlyPublishedFilter : {}) - }, - // Note this is the node update, not time of last publish - orderBy: { updatedAt: "desc" }, - take: size, - skip: (page - 1) * size, -}); + }, + where: { + ownerId, + isDeleted: false, + // Can't filter afterward with onlyPublished because then paging won't make sense + ...(onlyPublished ? onlyPublishedFilter : {}), + }, + // Note this is the node update, not time of last publish + orderBy: { updatedAt: 'desc' }, + take: size, + skip: (page - 1) * size, + }); const onlyPublishedFilter: { versions: Prisma.NodeVersionListRelationFilter } = { versions: { some: { - OR: [ - { transactionId: { not: null } }, - { commitId: { not: null } } - ] - } - } + OR: [{ transactionId: { not: null } }, { commitId: { not: null } }], + }, + }, }; diff --git a/desci-server/src/controllers/nodes/metadata.ts b/desci-server/src/controllers/nodes/metadata.ts index 0f6ec983a..677a5f938 100644 --- a/desci-server/src/controllers/nodes/metadata.ts +++ b/desci-server/src/controllers/nodes/metadata.ts @@ -3,15 +3,11 @@ import { ActionType } from '@prisma/client'; import { NextFunction, Response } from 'express'; import { z } from 'zod'; -import { - BadRequestError, - InternalError, - RequestWithNode, - SuccessMessageResponse, - SuccessResponse, - metadataClient, -} from '../../internal.js'; +import { BadRequestError, InternalError } from '../../core/ApiError.js'; +import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; +import { RequestWithNode } from '../../middleware/authorisation.js'; import { MetadataResponse } from '../../services/AutomatedMetadata.js'; +import { metadataClient } from '../../services/index.js'; import { saveInteraction } from '../../services/interactionLog.js'; import { isDoiLink } from '../data/utils.js'; diff --git a/desci-server/src/controllers/nodes/prepublish.ts b/desci-server/src/controllers/nodes/prepublish.ts index 934d077c8..2399c870c 100644 --- a/desci-server/src/controllers/nodes/prepublish.ts +++ b/desci-server/src/controllers/nodes/prepublish.ts @@ -4,12 +4,14 @@ import { NextFunction, Response } from 'express'; import { prisma } from '../../client.js'; import { logger as parentLogger } from '../../logger.js'; -import { RequestWithNode } from '../../middleware/authorisation.js'; +import { ensureNodeAccess, RequestWithNode } from '../../middleware/authorisation.js'; +import { delFromCache } from '../../redisClient.js'; import { updateManifestDataBucket } from '../../services/data/processing.js'; import { NodeUuid } from '../../services/manifestRepo.js'; import repoService from '../../services/repoService.js'; import { prepareDataRefsForDagSkeleton } from '../../utils/dataRefTools.js'; import { dagifyAndAddDbTreeToIpfs } from '../../utils/draftTreeUtils.js'; +import { ensureUuidEndsWithDot } from '../../utils.js'; import { persistManifest } from '../data/utils.js'; type PrepublishResponse = PrepublishSuccessResponse | PrepublishErrorResponse; @@ -45,6 +47,7 @@ export const prepublish = async (req: RequestWithNode, res: Response, _next: NextFunction) => { const { uuid, cid, manifest, transactionId, ceramicStream, commitId, useNewPublish } = req.body; - // debugger; const email = req.user.email; const logger = parentLogger.child({ // id: req.id, @@ -123,40 +126,8 @@ export const publish = async (req: PublishRequest, res: Response if (task) return res.status(400).json({ error: 'Node publishing in progress' }); - // let publishTask: PublishTaskQueue | undefined; logger.info({ ceramicStream, commitId, uuid, owner: owner.id }, 'Triggering new publish flow'); const dpidAlias = await syncPublish(ceramicStream, commitId, node, owner, cid, uuid, manifest); - // if (useNewPublish) { - // } else { - // publishTask = await prisma.publishTaskQueue.create({ - // data: { - // cid, - // dpid: manifest.dpid?.id, - // userId: owner.id, - // transactionId, - // ceramicStream, - // commitId, - // uuid: ensureUuidEndsWithDot(uuid), - // status: PublishTaskQueueStatus.WAITING, - // }, - // }); - // } - - saveInteraction( - req, - ActionType.PUBLISH_NODE, - { - cid, - dpid: dpidAlias?.toString() ?? manifest.dpid?.id, - userId: owner.id, - transactionId, - ceramicStream, - commitId, - uuid: ensureUuidEndsWithDot(uuid), - // status: PublishTaskQueueStatus.WAITING, - }, - owner.id, - ); updateAssociatedAttestations(node.uuid, dpidAlias ? dpidAlias.toString() : manifest.dpid?.id); @@ -178,13 +149,41 @@ export const publish = async (req: PublishRequest, res: Response version, }); + // Make sure we don't serve stale manifest state when a publish is happening + delFromCache(`node-draft-${ensureUuidEndsWithDot(node.uuid)}`); + + saveInteraction( + req, + ActionType.PUBLISH_NODE, + { + cid, + dpid: dpidAlias?.toString() ?? manifest.dpid?.id, + userId: owner.id, + transactionId, + ceramicStream, + commitId, + uuid: ensureUuidEndsWithDot(uuid), + outcome: 'SUCCESS', + }, + owner.id, + ); + return res.send({ ok: true, dpid: dpidAlias ?? parseInt(manifest.dpid?.id), - // taskId: publishTask?.id, }); } catch (err) { logger.error({ err }, '[publish::publish] node-publish-err'); + saveInteraction(req, ActionType.PUBLISH_NODE, { + cid, + user: req.user, + transactionId, + ceramicStream, + commitId, + uuid: ensureUuidEndsWithDot(uuid), + outcome: 'FAILURE', + err: stdSerializers.errWithCause(err as Error), + }); return res.status(400).send({ ok: false, error: err.message }); } }; @@ -283,9 +282,15 @@ const syncPublish = async ( await Promise.all(promises); + const dpid = dpidAlias?.toString() || legacyDpid?.toString(); // Intentionally of above stacked promise, needs the DPID to be resolved!!! // Send emails coupled to the publish event - await publishServices.handleDeferredEmails(node.uuid, dpidAlias?.toString() || legacyDpid?.toString()); + await publishServices.handleDeferredEmails(node.uuid, dpid); + + /* + * Emit notification on publish + */ + await emitNotificationOnPublish(node, owner, dpid); const targetDpidUrl = getTargetDpidUrl(); discordNotify({ message: `${targetDpidUrl}/${dpidAlias}` }); @@ -306,8 +311,39 @@ const createOrUpgradeDpidAlias = async ( ceramicStream: string, uuid: string, ): Promise => { + const logger = parentLogger.child({ + module: 'NODE::createOrUpgradeDpidAlias', + legacyDpid, + ceramicStream, + uuid, + }); + let dpidAlias: number; if (legacyDpid) { + // Use subgraph lookup to ensure we don't get the owner from the stream and compare with itself + const legacyHistory = await _getIndexedResearchObjects([uuid]); + + // On the initial legacy publish, the subgraph hasn't had time to index the event at this point. + // If it returns successfully, but array is empty, we can assume this is the first publish. + // and OK the check as there isn't any older history to contend with. + // This likely only happens in the legacy publish tests in nodes-lib, as legacy publish is disabled in the app. + const legacyOwner = legacyHistory.researchObjects[0]?.owner; + + const streamInfo = await directStreamLookup(ceramicStream); + if ('err' in streamInfo) { + logger.error(streamInfo, 'Failed to load stream when doing checks before upgrade'); + throw new Error('Failed to load stream'); + } + const streamController = streamInfo.state.metadata.controllers[0].toLowerCase(); + const differentOwner = legacyOwner?.toLowerCase() !== streamController.split(':').pop().toLowerCase(); + + // Caveat from above: if there was a legacyDpid, but no owner, we're likely in the middle of that process + // and nodes-lib has published both with the same key regardless + if (differentOwner && legacyOwner !== undefined) { + logger.error({ streamController, legacyOwner }, 'Legacy owner and stream controller differs'); + throw new Error('Legacy owner and stream controller differs'); + } + // Requires the REGISTRY_OWNER_PKEY to be set in env dpidAlias = await upgradeDpid(legacyDpid, ceramicStream); } else { @@ -471,10 +507,17 @@ export const publishHandler = async ({ const targetDpidUrl = getTargetDpidUrl(); discordNotify({ message: `${targetDpidUrl}/${manifest.dpid?.id}` }); + const dpid = node.dpidAlias?.toString() ?? manifest.dpid?.id; + /** * Fire off any deferred emails awaiting publish */ - await publishServices.handleDeferredEmails(node.uuid, node.dpidAlias?.toString() ?? manifest.dpid?.id); + await publishServices.handleDeferredEmails(node.uuid, dpid); + + /* + * Emit notification on publish + */ + await emitNotificationOnPublish(node, owner, dpid); /** * Save the cover art for this Node for later sharing: PDF -> JPG for this version diff --git a/desci-server/src/controllers/nodes/searchNodes.ts b/desci-server/src/controllers/nodes/searchNodes.ts index 4f7ced30a..eb7420f9c 100644 --- a/desci-server/src/controllers/nodes/searchNodes.ts +++ b/desci-server/src/controllers/nodes/searchNodes.ts @@ -5,9 +5,9 @@ import axios from 'axios'; import { Request, Response, NextFunction } from 'express'; import { prisma } from '../../client.js'; -import { resolveNodeManifest } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; import { getIndexedResearchObjects } from '../../theGraph.js'; +import { resolveNodeManifest } from '../../utils/manifest.js'; import { asyncMap, decodeBase64UrlSafeToHex, randomUUID64 } from '../../utils.js'; const logger = parentLogger.child({ diff --git a/desci-server/src/controllers/nodes/sharedNodes.ts b/desci-server/src/controllers/nodes/sharedNodes.ts index 9818ba033..37ddd3541 100644 --- a/desci-server/src/controllers/nodes/sharedNodes.ts +++ b/desci-server/src/controllers/nodes/sharedNodes.ts @@ -2,9 +2,9 @@ import { User } from '@prisma/client'; import { Request, Response } from 'express'; import { prisma } from '../../client.js'; -import { getLatestManifestFromNode } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; import { PRIV_SHARE_CONTRIBUTION_PREFIX } from '../../services/Contributors.js'; +import { getLatestManifestFromNode } from '../../services/manifestRepo.js'; export type SharedNode = { uuid: string; diff --git a/desci-server/src/controllers/nodes/show.ts b/desci-server/src/controllers/nodes/show.ts index b9a7d2c05..c6dc52fa0 100755 --- a/desci-server/src/controllers/nodes/show.ts +++ b/desci-server/src/controllers/nodes/show.ts @@ -1,17 +1,14 @@ import { ResearchObjectV1, RESEARCH_OBJECT_NODES_PREFIX } from '@desci-labs/desci-models'; import { Node } from '@prisma/client'; import axios from 'axios'; -import { Request, Response, NextFunction } from 'express'; +import { Response, NextFunction } from 'express'; import { CID } from 'multiformats/cid'; import { prisma } from '../../client.js'; import { PUBLIC_IPFS_PATH } from '../../config/index.js'; import { logger as parentLogger } from '../../logger.js'; import { RequestWithNode } from '../../middleware/authorisation.js'; -// import { NodeUuid } from '../../services/manifestRepo.js'; import { showNodeDraftManifest } from '../../services/nodeManager.js'; -// import repoService from '../../services/repoService.js'; -import { cleanupManifestUrl } from '../../utils/manifest.js'; import { ensureUuidEndsWithDot } from '../../utils.js'; const transformManifestWithHistory = (data: ResearchObjectV1, researchNode: Node) => { @@ -84,28 +81,7 @@ export const show = async (req: RequestWithNode, res: Response, next: NextFuncti return; } - // let gatewayUrl = discovery.manifestUrl; try { - // gatewayUrl = cleanupManifestUrl(gatewayUrl, req.query?.g as string); - // logger.trace({ gatewayUrl, uuid }, 'transforming manifest'); - - // // this can take >30s before it resolves or - // // even fail after a much longer time there by causing the slow loading of nodes - // // and a lot of failing `*.loggedIn` test in - // (discovery as any).manifestData = transformManifestWithHistory( - // (await axios.get(gatewayUrl, { timeout: 2000 })).data, - // discovery, - // ); - // // Add draft manifest document - // const nodeUuid = ensureUuidEndsWithDot(uuid) as NodeUuid; - // // for draft nodes we can do this asynchronously on the frontend - // const manifest = await repoService.getDraftManifest(nodeUuid); - - // logger.info({ manifest: !!manifest }, '[SHOW API GET DRAFT MANIFEST]'); - - // if (manifest) (discovery as any).manifestData = transformManifestWithHistory(manifest, discovery); - // delete (discovery as any).restBody; - // logger.info({}, 'Retrive DraftManifest For /SHOW'); logger.trace('[showNodeDraftManifest]::START'); (discovery as any).manifestData = await showNodeDraftManifest(discovery, req.query?.g as string); logger.trace('[showNodeDraftManifest]::END'); @@ -123,10 +99,8 @@ export const show = async (req: RequestWithNode, res: Response, next: NextFuncti const { data } = await axios.get(url); res.send(data); return; - } catch (e) { - logger.error({ e }, 'error'); - // res.status(404).send(); - // example + } catch (error) { + logger.error({ error }, 'error'); res.status(404).send(); } }; diff --git a/desci-server/src/controllers/nodes/thumbnails.ts b/desci-server/src/controllers/nodes/thumbnails.ts index b9f2afe52..9beea3f83 100644 --- a/desci-server/src/controllers/nodes/thumbnails.ts +++ b/desci-server/src/controllers/nodes/thumbnails.ts @@ -1,8 +1,8 @@ import type { Request, Response, NextFunction } from 'express'; import { prisma } from '../../client.js'; -import { NodeUuid } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; +import { NodeUuid } from '../../services/manifestRepo.js'; import { type ThumbnailMap, thumbnailsService } from '../../services/Thumbnails.js'; import { ensureUuidEndsWithDot } from '../../utils.js'; diff --git a/desci-server/src/controllers/notifications/create.ts b/desci-server/src/controllers/notifications/create.ts new file mode 100644 index 000000000..5a3c5db83 --- /dev/null +++ b/desci-server/src/controllers/notifications/create.ts @@ -0,0 +1,73 @@ +import { NotificationType, User, UserNotifications } from '@prisma/client'; +import { Request, Response } from 'express'; +import { z } from 'zod'; + +import { logger as parentLogger } from '../../logger.js'; +import { createUserNotification } from '../../services/NotificationService.js'; + +export const CreateNotificationSchema = z.object({ + userId: z.number(), + nodeUuid: z.string().optional(), + type: z.nativeEnum(NotificationType), + title: z.string(), + message: z.string(), + payload: z.record(z.unknown()).optional(), +}); + +interface AuthenticatedRequest extends Request { + user: User; +} + +export interface ErrorResponse { + error: string; + details?: z.ZodIssue[] | string; +} + +export const createNotification = async ( + req: AuthenticatedRequest & { body: z.infer }, + res: Response, +) => { + const logger = parentLogger.child({ + module: 'UserNotifications::CreateNotification', + userId: req.user?.id, + }); + + logger.info('Creating user notification'); + // + try { + if (!req.user) { + logger.warn('Unauthorized, check middleware'); + return res.status(401).json({ error: 'Unauthorized' } as ErrorResponse); + } + + const { id: userId } = req.user; + const notificationData = CreateNotificationSchema.parse({ ...req.body, userId }); + + const notification = await createUserNotification(notificationData, { throwOnDisabled: true }); + + logger.info({ notificationId: notification.id }, 'Successfully created user notification'); + return res.status(201).json(notification); + } catch (error) { + // debugger; + if (error instanceof z.ZodError) { + logger.warn({ error: error.errors }, 'Invalid request parameters'); + return res.status(400).json({ error: 'Invalid request parameters', details: error.errors } as ErrorResponse); + } + if (error instanceof Error) { + if (error.message === 'Node not found') { + logger.warn({ error }, 'Node not found'); + return res.status(404).json({ error: 'Node not found' } as ErrorResponse); + } + if (error.message === 'Node does not belong to the user') { + logger.warn({ error }, 'Node does not belong to the user'); + return res.status(403).json({ error: 'Node does not belong to the user' } as ErrorResponse); + } + if (error.message === 'Notification type is disabled for this user') { + logger.warn({ error }, 'Notification type is disabled for this user'); + return res.status(403).json({ error: 'Notification type is disabled for this user' } as ErrorResponse); + } + } + logger.error({ error }, 'Error creating user notification'); + return res.status(500).json({ error: 'Internal server error' } as ErrorResponse); + } +}; diff --git a/desci-server/src/controllers/notifications/index.ts b/desci-server/src/controllers/notifications/index.ts new file mode 100644 index 000000000..5cc3fa750 --- /dev/null +++ b/desci-server/src/controllers/notifications/index.ts @@ -0,0 +1,76 @@ +import { User, UserNotifications } from '@prisma/client'; +import { Request, Response } from 'express'; +import { z } from 'zod'; + +import { prisma } from '../../client.js'; +import { logger as parentLogger } from '../../logger.js'; +import { getUserNotifications } from '../../services/NotificationService.js'; + +export const GetNotificationsQuerySchema = z.object({ + page: z.string().regex(/^\d+$/).transform(Number).optional().default('1'), + perPage: z.string().regex(/^\d+$/).transform(Number).optional().default('25'), + dismissed: z + .enum(['true', 'false']) + .optional() + .transform((value) => value === 'true'), +}); + +interface AuthenticatedRequest extends Request { + user: User; +} + +export interface PaginatedResponse { + data: T[]; + pagination: { + currentPage: number; + totalPages: number; + totalItems: number; + }; +} + +export interface ErrorResponse { + error: string; + details?: z.ZodIssue[] | string; +} + +export const listUserNotifications = async ( + req: AuthenticatedRequest & { query: z.infer }, + res: Response | ErrorResponse>, +) => { + const logger = parentLogger.child({ + module: 'UserNotifications::GetUserNotifications', + userId: req.user?.id, + query: req.query, + }); + logger.info('Fetching user notifications'); + + try { + if (!req.user) { + logger.warn('Unauthorized, check middleware'); + return res.status(401).json({ error: 'Unauthorized' } as ErrorResponse); + } + + const { id: userId } = req.user; + const query = GetNotificationsQuerySchema.parse(req.query); + + const notifs = await getUserNotifications(userId, query); + + logger.info( + { + totalItems: notifs.pagination.totalItems, + page: notifs.pagination.currentPage, + totalPages: notifs.pagination.totalPages, + }, + 'Successfully fetched user notifications', + ); + + return res.status(200).json(notifs); + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn({ error: error.errors }, 'Invalid request parameters'); + return res.status(400).json({ error: 'Invalid request parameters', details: error.errors } as ErrorResponse); + } + logger.error({ error }, 'Error fetching user notifications'); + return res.status(500).json({ error: 'Internal server error' } as ErrorResponse); + } +}; diff --git a/desci-server/src/controllers/notifications/update.ts b/desci-server/src/controllers/notifications/update.ts new file mode 100644 index 000000000..ee793d8ed --- /dev/null +++ b/desci-server/src/controllers/notifications/update.ts @@ -0,0 +1,82 @@ +import { User, UserNotifications } from '@prisma/client'; +import { Request, Response } from 'express'; +import { z } from 'zod'; + +import { logger as parentLogger } from '../../logger.js'; +import { updateUserNotification, batchUpdateUserNotifications } from '../../services/NotificationService.js'; + +const UpdateDataSchema = z.object({ + dismissed: z.boolean().optional(), +}); + +const BatchUpdateSchema = z.object({ + notificationIds: z.array(z.number()), + updateData: UpdateDataSchema, +}); + +interface AuthenticatedRequest extends Request { + user: User; +} + +export interface ErrorResponse { + error: string; + details?: z.ZodIssue[] | string; +} + +type UpdateNotificationRequest = AuthenticatedRequest & { + params: { notificationId?: string }; + body: z.infer | z.infer; +}; + +export const updateNotification = async ( + req: UpdateNotificationRequest, + res: Response, +) => { + const logger = parentLogger.child({ + module: 'UserNotifications::UpdateNotification', + userId: req.user?.id, + }); + + logger.info({ params: req.params, body: req.body }, 'Updating user notification(s)'); + + try { + if (!req.user) { + logger.warn('Unauthorized, check middleware'); + return res.status(401).json({ error: 'Unauthorized' } as ErrorResponse); + } + + const { id: userId } = req.user; + + if (req.params.notificationId) { + // Single update + const notificationId = parseInt(req.params.notificationId); + const updateData = UpdateDataSchema.parse(req.body); + const updatedNotification = await updateUserNotification(notificationId, userId, updateData); + logger.info({ notificationId: updatedNotification.id }, 'Successfully updated user notification'); + return res.status(200).json(updatedNotification); + } else { + // Batch update + const { notificationIds, updateData } = BatchUpdateSchema.parse(req.body); + const count = await batchUpdateUserNotifications(notificationIds, userId, updateData); + logger.info({ count }, 'Successfully batch updated user notifications'); + return res.status(200).json({ count }); + } + } catch (error) { + if (error instanceof z.ZodError) { + logger.warn({ error: error.errors }, 'Invalid request parameters'); + return res.status(400).json({ error: 'Invalid request parameters', details: error.errors } as ErrorResponse); + } + if (error instanceof Error) { + if (error.message === 'Notification not found') { + logger.warn({ error }, 'Notification not found'); + return res.status(404).json({ error: 'Notification not found' } as ErrorResponse); + } + if (error.message === 'Notification does not belong to the user') { + logger.warn({ error }, 'Notification does not belong to the user'); + return res.status(403).json({ error: 'Notification does not belong to the user' } as ErrorResponse); + } + } + logger.error({ error }, 'Error updating user notification(s)'); + return res.status(500).json({ error: 'Internal server error' } as ErrorResponse); + } +}; diff --git a/desci-server/src/controllers/notifications/updateSettings.ts b/desci-server/src/controllers/notifications/updateSettings.ts new file mode 100644 index 000000000..197d9a03d --- /dev/null +++ b/desci-server/src/controllers/notifications/updateSettings.ts @@ -0,0 +1,49 @@ +import { User, NotificationType } from '@prisma/client'; +import { Request, Response } from 'express'; +import { z } from 'zod'; + +import { logger as parentLogger } from '../../logger.js'; +import { updateNotificationSettings } from '../../services/NotificationService.js'; + +const NotificationSettingsSchema = z.record(z.nativeEnum(NotificationType), z.boolean()); + +interface AuthenticatedRequest extends Request { + user: User; +} + +export interface ErrorResponse { + error: string; + details?: z.ZodIssue[] | string; +} + +export const updateSettings = async ( + req: AuthenticatedRequest & { body: z.infer }, + res: Response> | ErrorResponse>, +) => { + // debugger; + const logger = parentLogger.child({ + module: 'UserNotifications::UpdateSettings', + userId: req.user?.id, + }); + try { + if (!req.user) { + logger.warn('Unauthorized, check middleware'); + return res.status(401).json({ error: 'Unauthorized' } as ErrorResponse); + } + + const { id: userId } = req.user; + const settings = NotificationSettingsSchema.parse(req.body); + + const newSettings = await updateNotificationSettings(userId, settings); + + return res.status(200).json(newSettings); + } catch (error) { + // debugger; + if (error instanceof z.ZodError) { + logger.warn({ error: error.errors }, 'Invalid request parameters'); + return res.status(400).json({ error: 'Invalid request parameters', details: error.errors } as ErrorResponse); + } + logger.error({ error }, 'Error updating user notification settings'); + return res.status(500).json({ error: 'Internal server error' } as ErrorResponse); + } +}; diff --git a/desci-server/src/controllers/users/associateWallet.ts b/desci-server/src/controllers/users/associateWallet.ts index d57cf0d7f..46978511d 100755 --- a/desci-server/src/controllers/users/associateWallet.ts +++ b/desci-server/src/controllers/users/associateWallet.ts @@ -5,14 +5,8 @@ import { NextFunction, Request, Response } from 'express'; import { ErrorTypes, SiweMessage, generateNonce } from 'siwe'; import { prisma } from '../../client.js'; -import { - AuthFailureError, - BadRequestError, - ForbiddenError, - SuccessMessageResponse, - SuccessResponse, - extractTokenFromCookie, -} from '../../internal.js'; +import { AuthFailureError, BadRequestError, ForbiddenError } from '../../core/ApiError.js'; +import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; import { getUserConsent, saveInteraction } from '../../services/interactionLog.js'; import { writeExternalIdToOrcidProfile } from '../../services/user.js'; @@ -57,7 +51,7 @@ export const associateOrcidWallet = async (req: Request, res: Response, next: Ne if (hasOrcidWallet > 0) { res.status(202).send({ message: 'orcid DID already registered to this user' }); return; - }; + } // TODO: check for wallet uniqueness across all accounts const doesExist = @@ -69,7 +63,7 @@ export const associateOrcidWallet = async (req: Request, res: Response, next: Ne if (doesExist) { res.status(400).send({ err: 'orcid DID already register to some other' }); return; - }; + } try { const addWallet = await prisma.wallet.create({ @@ -312,7 +306,7 @@ const sendGiftTxn = async (user: User, walletAddress: string, addedWalletId: num */ const giftedWallets = await prisma.wallet.count({ where: { - user, + userId: user.id, giftTransaction: { not: null }, }, }); diff --git a/desci-server/src/controllers/users/search.ts b/desci-server/src/controllers/users/search.ts index 4307f3e64..0b1df6a27 100644 --- a/desci-server/src/controllers/users/search.ts +++ b/desci-server/src/controllers/users/search.ts @@ -17,7 +17,7 @@ export type SearchProfilesResBody = error: string; }; -export type UserProfile = { name: string; id: number; orcid?: string }; +export type UserProfile = { name: string; id: number; orcid?: string; organisations?: string[] }; export const searchProfiles = async (req: SearchProfilesRequest, res: Response) => { // debugger; @@ -45,13 +45,21 @@ export const searchProfiles = async (req: SearchProfilesRequest, res: Response ({ name: profile.name, id: profile.id, + organisations: profile.userOrganizations.map((org) => org.organization.name), ...(profile.orcid && { orcid: profile.orcid }), })); return res.status(200).json({ profiles: profilesReturn }); diff --git a/desci-server/src/core/ApiError.ts b/desci-server/src/core/ApiError.ts index c4960f3fa..0f8a45318 100644 --- a/desci-server/src/core/ApiError.ts +++ b/desci-server/src/core/ApiError.ts @@ -1,7 +1,5 @@ import { Response } from 'express'; -import { AttestationError, AttestationErrorType } from '../internal.js'; - import { AuthFailureResponse, BadRequestResponse, @@ -10,6 +8,7 @@ import { NotFoundResponse, UnProcessableRequestResponse, } from './ApiResponse.js'; +import { AttestationError, AttestationErrorType } from './communities/error.js'; import { DoiError, DoiErrorType } from './doi/error.js'; export enum ApiErrorType { diff --git a/desci-server/src/index.ts b/desci-server/src/index.ts index 5b224b49a..353c29d0b 100755 --- a/desci-server/src/index.ts +++ b/desci-server/src/index.ts @@ -1,6 +1,115 @@ +import { errWithCause } from 'pino-std-serializers'; + +import { prisma } from './client.js'; +import { logger as parentLogger } from './logger.js'; +import { redisClient, lockService } from './redisClient.js'; import { server } from './server.js'; +import { SubmissionQueueJob } from './workers/doiSubmissionQueue.js'; + +const logger = parentLogger.child({ + module: 'index.ts', +}); server.ready().then((_) => { console.log('server is ready'); }); export const app = server.app; + +/** + * ALL process lifecycle hooks goes below, because: + * 1. it prevents import cycles + * 2. it makes sure we don't overwrite a hook already defined in another module + */ + +/** Tracks ongoing graceful exit progress */ +let isShuttingDown = false; + +/** Async cleanup tasks we try to do before exiting */ +const cleanup = async () => { + if (isShuttingDown) return; + isShuttingDown = true; + + try { + logger.info('Starting cleanup'); + + // Stop accepting new requests before initialising cleanup + new Promise((resolve, reject) => { + server.server.close((err) => { + if (err) reject(err); + else resolve(undefined); + }); + }); + + await Promise.allSettled([ + lockService.freeLocks(), + redisClient.quit(), + SubmissionQueueJob.stop(), + prisma.$disconnect(), + ]); + } catch (err) { + logger.error({ err }, 'Cleanup failed'); + } +}; + +/** Async shutdown to use as signal hooks. + * Notably, this will not work for + * - `exit` (ignores async tasks) + * - `SIGKILL` (the kernel will kill us basically immediately, can't be caught) + */ +const gracefulShutdown = async (signal: string) => { + try { + logger.info(`Process caught ${signal}, starting graceful shutdown`); + await Promise.race([ + cleanup(), + new Promise((_, reject) => setTimeout(() => reject(new Error('Cleanup timed out')), 5000)), + ]); + } catch (err) { + logger.error({ err }, 'Graceful shutdown failed, exiting anyway'); + } finally { + process.exit(1); + } +}; + +// catches ctrl+c event +process.on('SIGINT', () => gracefulShutdown('SIGINT')); +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +// default kill signal for nodemon +process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2')); + +/** Enough to make sure logs are writted to stdout even when choking under load, + * but if this has passed, ghetto-log and allow the proces to die. + */ +const FATAL_TIMEOUT_MS = 500; + +/** Handler to use for uncaught exceptions and promise rejections, from which we + * want to extract as much log info as possible to the log aggregator. + */ +const handleFatalError = async (error: unknown, type: string) => { + // Mostly error should be an Error, but in theory anything can be thrown + const normalizedError = error instanceof Error ? error : new Error(String(error)); + + try { + const logPromise = logger.fatal({ err: errWithCause(normalizedError), type }, 'Process got fatal error'); + await Promise.race([ + logPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Logging timed out')), FATAL_TIMEOUT_MS)), + ]); + await cleanup(); + } catch (loggingError) { + console.error('Process got fatal error, and logger call timed out', { + err: errWithCause(normalizedError), + loggingError, + }); + } finally { + process.exit(1); + } +}; + +process.on('uncaughtException', (err) => handleFatalError(err, 'uncaughtException')); +process.on('unhandledRejection', (reason) => handleFatalError(reason, 'unhandledRejection')); + +process.on('exit', () => { + if (!isShuttingDown) { + logger.error('Process exiting without cleanup - this should not happen'); + } +}); diff --git a/desci-server/src/internal.ts b/desci-server/src/internal.ts deleted file mode 100644 index 263a2404c..000000000 --- a/desci-server/src/internal.ts +++ /dev/null @@ -1,19 +0,0 @@ -export * from './logger.js'; -export * from './client.js'; -export * from './core/communities/error.js'; -export * from './services/index.js'; -export * from './core/ApiError.js'; -export * from './core/ApiResponse.js'; -export * from './middleware/index.js'; -export * from './services/Communities.js'; -export * from './services/Attestation.js'; -export * from './utils/asyncHandler.js'; -export * from './routes/v1/attestations/schema.js'; -export * from './controllers/communities/index.js'; -export * from './controllers/attestations/index.js'; -export * from './controllers/doi/index.js'; -export * from './core/ApiResponse.js'; -export * from './utils/manifest.js'; -export * from './services/repoService.js'; -export * from './services/manifestRepo.js'; -export * from './utils.js'; diff --git a/desci-server/src/logger.ts b/desci-server/src/logger.ts index 59970ec9b..10e7b6d3c 100644 --- a/desci-server/src/logger.ts +++ b/desci-server/src/logger.ts @@ -122,6 +122,12 @@ export const logger = pino({ '*.authorization', '*.Authorization', 'Authorization', + 'access_token', + 'refresh_token', + '*.access_token', + '*.refresh_token', + 'jwtToken', + '*.jwtToken', ], }, }); @@ -132,14 +138,3 @@ function omitBuffer(array) { return rest; }); } - -// These should probably exit the process as it is not safe to continue -// execution after a generic exception has occurred: -// https://nodejs.org/api/process.html#warning-using-uncaughtexception-correctly -process.on('uncaughtException', (err) => { - logger.fatal(err, 'uncaught exception'); -}); - -process.on('unhandledRejection', (reason, promise) => { - logger.fatal({ reason, promise }, 'unhandled rejection'); -}); diff --git a/desci-server/src/middleware/errorHandler.ts b/desci-server/src/middleware/errorHandler.ts index 690083ea9..cbd5b20c5 100755 --- a/desci-server/src/middleware/errorHandler.ts +++ b/desci-server/src/middleware/errorHandler.ts @@ -1,6 +1,7 @@ import { Request, Response, NextFunction } from 'express'; -import { ApiError, logger } from '../internal.js'; +import { ApiError } from '../core/ApiError.js'; +import { logger } from '../logger.js'; import { CustomError } from '../utils/response/custom-error/CustomError.js'; export const errorHandler = (err: Error | CustomError, req: Request, res: Response, next: NextFunction) => { diff --git a/desci-server/src/middleware/permissions.ts b/desci-server/src/middleware/permissions.ts index 112f2e5c0..a69cc6962 100644 --- a/desci-server/src/middleware/permissions.ts +++ b/desci-server/src/middleware/permissions.ts @@ -1,6 +1,7 @@ import { User } from '@prisma/client'; import { NextFunction, Request as ExpressRequest, Response } from 'express'; import jwt from 'jsonwebtoken'; +import { Socket, ExtendedError } from 'socket.io'; import { prisma } from '../client.js'; import { hashApiKey } from '../controllers/auth/utils.js'; @@ -21,6 +22,7 @@ export const ensureUser = async (req: ExpressRequest, res: Response, next: NextF const retrievedUser = authTokenRetrieval || apiKeyRetrieval; if (!retrievedUser) { + // logger.trace({ token, apiKey }, 'ENSURE USER'); res.status(401).send({ ok: false, message: 'Unauthorized' }); } else { (req as any).user = retrievedUser; @@ -50,6 +52,50 @@ export const extractAuthToken = async (request: ExpressRequest | Request) => { return token; }; +export interface AuthenticatedSocket extends Socket { + data: { + userId: number | string; + }; +} + +/** + * Socket.IO WS: Authentication Middleware + */ +export const socketsEnsureUser = async (socket: Socket, next: (err?: ExtendedError) => void) => { + // debugger; + const cookies = parseWsCookies(socket.handshake.headers.cookie); + if (!cookies) { + return next(new Error('Authentication error: No cookies provided')); + } + const token = cookies[AUTH_COOKIE_FIELDNAME] as string | undefined; + const ip = + socket.handshake.headers['x-forwarded-for'] || + socket.handshake.address || + socket.handshake.headers['x-real-ip'] || + socket.conn.remoteAddress; + + logger.trace({ module: 'SocketEnsureUser Middleware', ip }, 'Attempting socketIO auth'); + + if (!token) { + logger.trace({ module: 'SocketEnsureUser Middleware', token, ip }, 'No token provided'); + return next(new Error('Authentication error: No token provided')); + } + + try { + const extractedUser = await extractUserFromToken(token); + if (!extractedUser) { + logger.trace({ module: 'SocketEnsureUser Middleware', ip }, 'Invalid token provided'); + return next(new Error('Authentication error: Invalid token provided')); + } + + (socket as AuthenticatedSocket).data.userId = extractedUser.id; + next(); + } catch (error) { + logger.error({ module: 'SocketEnsureUser Middleware', error, ip }, 'Authentication error'); + next(new Error('Authentication error: Server error')); + } +}; + /** * Extract Any token from IncommingRequest (Auth Bearer or Cookie or Cookies) */ @@ -190,3 +236,17 @@ export const extractUserFromApiKey = (apiKey: string, ip: string): Promise { + const [name, value] = cookie.trim().split('='); + cookies[name] = decodeURIComponent(value); + return cookies; + }, + {} as { [key: string]: string }, + ); +} diff --git a/desci-server/src/middleware/validator.ts b/desci-server/src/middleware/validator.ts index 88b6a1f4f..52157d931 100644 --- a/desci-server/src/middleware/validator.ts +++ b/desci-server/src/middleware/validator.ts @@ -1,7 +1,8 @@ import { NextFunction, Request, Response } from 'express'; import { ZodError, z } from 'zod'; -import { BadRequestError, InternalError, asyncHandler, logger } from '../internal.js'; +import { BadRequestError, InternalError } from '../core/ApiError.js'; +import { asyncHandler } from '../utils/asyncHandler.js'; export const validate = (schema: z.ZodObject) => asyncHandler(async (req: Request, res: Response, next: NextFunction) => { @@ -9,9 +10,8 @@ export const validate = (schema: z.ZodObject) => await schema.parseAsync(req); next(); } catch (err) { - // logger.error({ body: req.body, file: req.file }, 'zod validator'); if (err instanceof ZodError) { - console.log('Error', err); + // console.log('Error', err); throw new BadRequestError( err.errors.map((err) => err.message).join(','), err.issues.map((err) => `${err.path[err.path.length - 1]}: ${err.message}`), diff --git a/desci-server/src/redisClient.ts b/desci-server/src/redisClient.ts index 0852cd0b9..c61e56c23 100644 --- a/desci-server/src/redisClient.ts +++ b/desci-server/src/redisClient.ts @@ -3,7 +3,6 @@ import os from 'os'; import { createClient } from 'redis'; import { logger as parentLogger } from './logger.js'; -import { SubmissionQueueJob } from './workers/doiSubmissionQueue.js'; const hostname = os.hostname(); const logger = parentLogger.child({ @@ -11,7 +10,7 @@ const logger = parentLogger.child({ hostname, }); -const redisClient = createClient({ +export const redisClient = createClient({ // url: process.env.REDIS_URL, socket: { host: process.env.REDIS_HOST || 'localhost', @@ -49,17 +48,14 @@ redisClient.on('error', (err) => { logger.error({ err }, 'Redis Client Error'); }); -// gracefully shutdown -// process.on('exit', () => { -// redisClient.quit(); -// }); +export const ONE_WEEK_TTL = 60 * 60 * 24 * 7; +export const ONE_DAY_TTL = 60 * 60 * 24; +export const DEFAULT_TTL = ONE_WEEK_TTL; -export default redisClient; - -const DEFAULT_TTL = 60 * 60 * 24 * 7; // 1 week -export const ONE_DAY_TTL = 60 * 60 * 24; // 1 week - -export async function getFromCache(key: string): Promise { +/** + * Get a value from cache, and optionally configure its on-hit TTL refresh + */ +export async function getFromCache(key: string, ttl?: number): Promise { let clientAvailable = true; if (!redisClient.isOpen) { @@ -71,7 +67,7 @@ export async function getFromCache(key: string): Promise { const result = await redisClient.get(key); if (result !== null) { logger.info(`[REDIS CACHE]${key} retrieved from cache`); - redisClient.expire(key, DEFAULT_TTL); + redisClient.expire(key, ttl || DEFAULT_TTL); return JSON.parse(result); } } @@ -79,19 +75,29 @@ export async function getFromCache(key: string): Promise { return null; } +export async function delFromCache(key: string): Promise { + if (!redisClient.isOpen) { + logger.info(`[REDIS CACHE] skipped-no-conn: DEL ${key}`); + return; + } + + await redisClient.del(key); + logger.info(`[REDIS CACHE] DEL ${key}`); +} + export async function setToCache(key: string, value: T, ttl = DEFAULT_TTL): Promise { if (!redisClient.isOpen) { - logger.info(`[REDIS CACHE] skipped-no-conn ${key}`); + logger.info(`[REDIS CACHE] skipped-no-conn: SET ${key}`); return; } await redisClient.set(key, JSON.stringify(value), { EX: ttl }); - logger.info(`[REDIS CACHE]${key} cached`); + logger.info(`[REDIS CACHE] SET ${key}`); } export async function getOrCache(key: string, fn: () => Promise, ttl = DEFAULT_TTL): Promise { try { - const cachedValue = await getFromCache(key); + const cachedValue = await getFromCache(key, ttl); if (cachedValue !== null) { return cachedValue; } @@ -147,27 +153,3 @@ class SingleNodeLockService { } export const lockService = new SingleNodeLockService(); - -process.on('exit', () => { - logger.info('Process caught exit'); - lockService.freeLocks(); - redisClient.quit(); - SubmissionQueueJob.stop(); -}); - -// catches ctrl+c event -process.on('SIGINT', () => { - logger.info('Process caught SIGINT'); - process.exit(1); -}); - -process.on('SIGTERM', () => { - logger.info('Process caught SIGTERM'); - process.exit(1); -}); - -// default kill signal for nodemon -process.on('SIGUSR2', () => { - logger.info('Process caught SIGUSR2'); - process.exit(1); -}); diff --git a/desci-server/src/routes/index.ts b/desci-server/src/routes/index.ts index d4fb60f3e..85660009d 100755 --- a/desci-server/src/routes/index.ts +++ b/desci-server/src/routes/index.ts @@ -1,7 +1,8 @@ import { Router } from 'express'; +import { handleCrossrefNotificationCallback } from '../controllers/doi/mint.js'; import { resolve } from '../controllers/raw/resolve.js'; -import { asyncHandler, handleCrossrefNotificationCallback } from '../internal.js'; +import { asyncHandler } from '../utils/asyncHandler.js'; import page404 from './pages/404.js'; import pageRoot from './pages/root.js'; diff --git a/desci-server/src/routes/v1/admin/communities/index.ts b/desci-server/src/routes/v1/admin/communities/index.ts index e96b9d210..0e26d92b5 100644 --- a/desci-server/src/routes/v1/admin/communities/index.ts +++ b/desci-server/src/routes/v1/admin/communities/index.ts @@ -9,13 +9,20 @@ import { createAttestation, createCommunity, listAllCommunities, + listAttestations, + listCommunityAttestations, + listCommunityEntryAttestations, removeEntryAttestation, removeMember, updateAttestation, updateCommunity, } from '../../../../controllers/admin/communities/index.js'; -import { asyncHandler, ensureAdmin, ensureUser, logger as parentLogger, validate } from '../../../../internal.js'; +import { logger as parentLogger } from '../../../../logger.js'; +import { ensureAdmin } from '../../../../middleware/ensureAdmin.js'; +import { ensureUser } from '../../../../middleware/permissions.js'; +import { validate } from '../../../../middleware/validator.js'; import { isS3Configured, s3Client } from '../../../../services/s3.js'; +import { asyncHandler } from '../../../../utils/asyncHandler.js'; import { addAttestationSchema, @@ -51,14 +58,15 @@ const upload = isS3Configured : multer({ preservePath: true }); const uploadHandler = upload.fields([ - { name: 'imageUrl', maxCount: 1 }, - { name: 'verifiedImageUrl', maxCount: 1 }, + { name: 'image', maxCount: 1 }, + { name: 'verifiedImage', maxCount: 1 }, ]); const wrappedHandler = (req: Request, res: Response, next: NextFunction) => { uploadHandler(req, res, (err) => { if (err) { if (err instanceof multer.MulterError) { + logger.error({ err, files: req.files }, 'MulterError encountered'); throw err; } else { logger.error({ err }, 'Upload Handler Error encountered'); @@ -78,6 +86,8 @@ const sanitizeBody = async (req: Request, _res: Response, next: NextFunction) => }; router.get('/', [ensureUser, ensureAdmin], asyncHandler(listAllCommunities)); +router.get('/:communityId/attestations', [ensureUser, ensureAdmin], asyncHandler(listCommunityAttestations)); +router.get('/:communityId/entryAttestations', [ensureUser, ensureAdmin], asyncHandler(listCommunityEntryAttestations)); router.post( '/', @@ -103,10 +113,10 @@ router.put( asyncHandler(updateAttestation), ); -router.post('/:communityId/members', [ensureUser, ensureAdmin, validate(addMemberSchema)], asyncHandler(addMember)); +router.post('/:communityId/addMember', [ensureUser, ensureAdmin, validate(addMemberSchema)], asyncHandler(addMember)); router.delete( - '/:communityId/members/:memberId', + '/:communityId/removeMember/:memberId', [ensureUser, ensureAdmin, validate(removeMemberSchema)], asyncHandler(removeMember), ); @@ -117,7 +127,7 @@ router.post( asyncHandler(addEntryAttestation), ); -router.delete( +router.post( '/:communityId/removeEntryAttestation/:attestationId', [ensureUser, ensureAdmin, validate(addEntryAttestationSchema)], asyncHandler(removeEntryAttestation), diff --git a/desci-server/src/routes/v1/admin/index.ts b/desci-server/src/routes/v1/admin/index.ts index 561a9e111..56cd8ed47 100644 --- a/desci-server/src/routes/v1/admin/index.ts +++ b/desci-server/src/routes/v1/admin/index.ts @@ -1,12 +1,15 @@ import { Router } from 'express'; import { createCsv, getAnalytics } from '../../../controllers/admin/analytics.js'; +import { listAttestations } from '../../../controllers/admin/communities/index.js'; import { debugAllNodesHandler, debugNodeHandler } from '../../../controllers/admin/debug.js'; -import { listDoiRecords } from '../../../internal.js'; +import { listDoiRecords } from '../../../controllers/doi/admin.js'; import { ensureAdmin } from '../../../middleware/ensureAdmin.js'; import { ensureUser } from '../../../middleware/permissions.js'; +import { asyncHandler } from '../../../utils/asyncHandler.js'; import communities from './communities/index.js'; +import usersRouter from './users/index.js'; const router = Router(); @@ -18,5 +21,7 @@ router.get('/debug', [ensureUser, ensureAdmin], debugAllNodesHandler); router.get('/debug/:uuid', [ensureUser, ensureAdmin], debugNodeHandler); router.use('/communities', [ensureUser, ensureAdmin], communities); +router.get('/attestations', [ensureUser, ensureAdmin], asyncHandler(listAttestations)); +// router.use('/users', [ensureUser, ensureAdmin], usersRouter); export default router; diff --git a/desci-server/src/routes/v1/admin/users/index.ts b/desci-server/src/routes/v1/admin/users/index.ts new file mode 100644 index 000000000..9c26843fa --- /dev/null +++ b/desci-server/src/routes/v1/admin/users/index.ts @@ -0,0 +1,20 @@ +import { NextFunction, Response, Router } from 'express'; + +import { SuccessResponse } from '../../../../core/ApiResponse.js'; +import { ensureAdmin } from '../../../../middleware/ensureAdmin.js'; +import { ensureUser } from '../../../../middleware/permissions.js'; +import { asyncHandler } from '../../../../utils/asyncHandler.js'; + +// const logger = parentLogger.child({ module: 'Admin/communities' }); +const router = Router(); + +router.get( + '/search', + [ensureUser, ensureAdmin], + asyncHandler(async (_req: Request, res: Response, _next: NextFunction) => { + // + new SuccessResponse([]).send(res); + }), +); + +export default router; diff --git a/desci-server/src/routes/v1/attestations/index.ts b/desci-server/src/routes/v1/attestations/index.ts index 9c17c7e04..b40473785 100644 --- a/desci-server/src/routes/v1/attestations/index.ts +++ b/desci-server/src/routes/v1/attestations/index.ts @@ -1,26 +1,21 @@ import { Router } from 'express'; +import { claimAttestation, claimEntryRequirements, removeClaim } from '../../../controllers/attestations/claims.js'; +import { addComment, getAttestationComments, removeComment } from '../../../controllers/attestations/comments.js'; +import { addReaction, getAttestationReactions, removeReaction } from '../../../controllers/attestations/reactions.js'; import { - addComment, - addReaction, - addVerification, - removeComment, - removeReaction, - removeVerification, - getAttestationComments, getAllRecommendations, - showNodeAttestations, - claimAttestation, - claimEntryRequirements, - removeClaim, - getAttestationReactions, - showCommunityClaims, - getAttestationVerifications, - validate, getValidatedRecommendations, - asyncHandler, -} from '../../../internal.js'; +} from '../../../controllers/attestations/recommendations.js'; +import { showCommunityClaims, showNodeAttestations } from '../../../controllers/attestations/show.js'; +import { + addVerification, + getAttestationVerifications, + removeVerification, +} from '../../../controllers/attestations/verification.js'; import { ensureUser } from '../../../middleware/permissions.js'; +import { validate } from '../../../middleware/validator.js'; +import { asyncHandler } from '../../../utils/asyncHandler.js'; import { claimAttestationSchema, diff --git a/desci-server/src/routes/v1/attestations/schema.ts b/desci-server/src/routes/v1/attestations/schema.ts index 5e1721e5e..60ea61243 100644 --- a/desci-server/src/routes/v1/attestations/schema.ts +++ b/desci-server/src/routes/v1/attestations/schema.ts @@ -134,7 +134,7 @@ const commentSchema = z .refine((links) => links.every((link) => dpidPathRegex.test(link))) .optional(), highlights: z.array(highlightBlockSchema).optional(), - uuid: z.string().optional(), + uuid: z.string(), visible: z.boolean().default(true), }) .refine((comment) => comment.body?.length > 0 || !!comment?.highlights?.length, { diff --git a/desci-server/src/routes/v1/auth.ts b/desci-server/src/routes/v1/auth.ts index 96f0d6a0d..5d2f7ad35 100755 --- a/desci-server/src/routes/v1/auth.ts +++ b/desci-server/src/routes/v1/auth.ts @@ -17,8 +17,8 @@ import { check, } from '../../controllers/auth/index.js'; import { walletLogin, walletNonce } from '../../controllers/users/associateWallet.js'; -import { asyncHandler } from '../../internal.js'; import { ensureUser } from '../../middleware/permissions.js'; +import { asyncHandler } from '../../utils/asyncHandler.js'; const router = Router(); diff --git a/desci-server/src/routes/v1/communities/index.ts b/desci-server/src/routes/v1/communities/index.ts index f37195448..ba157424c 100644 --- a/desci-server/src/routes/v1/communities/index.ts +++ b/desci-server/src/routes/v1/communities/index.ts @@ -1,18 +1,16 @@ import { Router } from 'express'; import { - asyncHandler, - checkMemberGuard, - ensureUser, - getAllFeeds, - getCommunityDetails, - getCommunityFeed, - getCommunityRadar, getCommunityRecommendations, getValidatedAttestations, - listCommunities, - validate, -} from '../../../internal.js'; +} from '../../../controllers/attestations/recommendations.js'; +import { getAllFeeds, getCommunityDetails, getCommunityFeed } from '../../../controllers/communities/feed.js'; +import { checkMemberGuard } from '../../../controllers/communities/guard.js'; +import { listCommunities } from '../../../controllers/communities/list.js'; +import { getCommunityRadar } from '../../../controllers/communities/radar.js'; +import { ensureUser } from '../../../middleware/permissions.js'; +import { validate } from '../../../middleware/validator.js'; +import { asyncHandler } from '../../../utils/asyncHandler.js'; import { getCommunityDetailsSchema, getCommunityFeedSchema, memberGuardSchema } from './schema.js'; diff --git a/desci-server/src/routes/v1/crossref.ts b/desci-server/src/routes/v1/crossref.ts index fd62d5576..9df370cad 100644 --- a/desci-server/src/routes/v1/crossref.ts +++ b/desci-server/src/routes/v1/crossref.ts @@ -1,14 +1,10 @@ // crossref/callback import { NextFunction, Request, Response, Router } from 'express'; -import { - AuthFailureError, - BadRequestError, - RequestWithCrossRefPayload, - asyncHandler, - handleCrossrefNotificationCallback, - logger as parentLogger, -} from '../../internal.js'; +import { handleCrossrefNotificationCallback, RequestWithCrossRefPayload } from '../../controllers/doi/mint.js'; +import { AuthFailureError } from '../../core/ApiError.js'; +import { logger as parentLogger } from '../../logger.js'; +import { asyncHandler } from '../../utils/asyncHandler.js'; const logger = parentLogger.child({ module: 'CROSSREF NOTIFICATION' }); const DEFAULT_CROSSREF_ENDPOINT = 'endpoint'; diff --git a/desci-server/src/routes/v1/doi.ts b/desci-server/src/routes/v1/doi.ts index 53686d531..ea48ee9b2 100644 --- a/desci-server/src/routes/v1/doi.ts +++ b/desci-server/src/routes/v1/doi.ts @@ -1,19 +1,16 @@ import { Router } from 'express'; -import { - asyncHandler, - checkMintability, - ensureNodeAccess, - ensureUser, - retrieveDoi, - retrieveDoiSchema, - mintDoi, - validate, -} from '../../internal.js'; +import { checkMintability, retrieveDoi } from '../../controllers/doi/check.js'; +import { mintDoi } from '../../controllers/doi/mint.js'; +import { retrieveDoiSchema } from '../../controllers/doi/schema.js'; +import { ensureNodeAccess } from '../../middleware/authorisation.js'; +import { ensureUser } from '../../middleware/permissions.js'; +import { validate } from '../../middleware/validator.js'; +import { asyncHandler } from '../../utils/asyncHandler.js'; const router = Router(); -router.post('/check/:uuid', [ensureUser, ensureNodeAccess], asyncHandler(checkMintability)); +router.get('/check/:uuid', [ensureUser, ensureNodeAccess], asyncHandler(checkMintability)); router.post('/mint/:uuid', [ensureUser, ensureNodeAccess], asyncHandler(mintDoi)); router.get('/', [validate(retrieveDoiSchema)], asyncHandler(retrieveDoi)); diff --git a/desci-server/src/routes/v1/index.ts b/desci-server/src/routes/v1/index.ts index 616777fc6..c78b5182f 100755 --- a/desci-server/src/routes/v1/index.ts +++ b/desci-server/src/routes/v1/index.ts @@ -7,8 +7,8 @@ import { handleCrossrefNotificationCallback } from '../../controllers/doi/mint.j import { queryRor } from '../../controllers/proxy/index.js'; import { ipfsReadGatewayProxy } from '../../controllers/proxy/ipfsReadGateway.js'; import { nft } from '../../controllers/raw/nft.js'; -import { asyncHandler } from '../../internal.js'; import { ensureUser } from '../../middleware/permissions.js'; +import { asyncHandler } from '../../utils/asyncHandler.js'; import admin from './admin/index.js'; import attestations from './attestations/index.js'; @@ -19,6 +19,7 @@ import data from './data.js'; import doi from './doi.js'; import log from './log.js'; import nodes from './nodes.js'; +import notifications from './notifications.js'; import pub from './pub.js'; import referral from './referral.js'; import search from './search.js'; @@ -60,6 +61,7 @@ router.use('/communities', communities); router.use('/attestations', attestations); router.use('/doi', doi); router.use('/search', search); +router.use('/notifications', notifications); router.get('/nft/:id', nft); router.use('/referral', referral); diff --git a/desci-server/src/routes/v1/nodes.ts b/desci-server/src/routes/v1/nodes.ts index d6281514e..6581df843 100755 --- a/desci-server/src/routes/v1/nodes.ts +++ b/desci-server/src/routes/v1/nodes.ts @@ -58,14 +58,14 @@ import { } from '../../controllers/nodes/index.js'; import { retrieveTitle } from '../../controllers/nodes/legacyManifestApi.js'; import { preparePublishPackage } from '../../controllers/nodes/preparePublishPackage.js'; -// import { prepublish } from '../../controllers/nodes/prepublish.js'; -// import { searchNodes } from '../../controllers/nodes/searchNodes.js'; -// import { listSharedNodes } from '../../controllers/nodes/sharedNodes.js'; -// import { thumbnails } from '../../controllers/nodes/thumbnails.js'; -// import { versionDetails } from '../../controllers/nodes/versionDetails.js'; -import { asyncHandler, attachUser, validate, ensureUserIfPresent, getCommentsSchema } from '../../internal.js'; +import { attachUser } from '../../middleware/attachUser.js'; import { ensureNodeAccess, ensureWriteNodeAccess } from '../../middleware/authorisation.js'; +import { ensureUserIfPresent } from '../../middleware/ensureUserIfPresent.js'; import { ensureUser } from '../../middleware/permissions.js'; +import { validate } from '../../middleware/validator.js'; +import { asyncHandler } from '../../utils/asyncHandler.js'; + +import { getCommentsSchema } from './attestations/schema.js'; const router = Router(); diff --git a/desci-server/src/routes/v1/notifications.ts b/desci-server/src/routes/v1/notifications.ts new file mode 100644 index 000000000..795a0abf7 --- /dev/null +++ b/desci-server/src/routes/v1/notifications.ts @@ -0,0 +1,17 @@ +import { Router } from 'express'; + +import { createNotification } from '../../controllers/notifications/create.js'; +import { listUserNotifications } from '../../controllers/notifications/index.js'; +import { updateNotification } from '../../controllers/notifications/update.js'; +import { updateSettings } from '../../controllers/notifications/updateSettings.js'; +import { ensureUser } from '../../middleware/permissions.js'; + +const router = Router(); + +router.get('/', [ensureUser], listUserNotifications); +router.post('/', [ensureUser], createNotification); +router.patch('/', [ensureUser], updateNotification); // Batch update route +router.patch('/settings', [ensureUser], updateSettings); +router.patch('/:notificationId', [ensureUser], updateNotification); + +export default router; diff --git a/desci-server/src/routes/v1/search.ts b/desci-server/src/routes/v1/search.ts index 429e18417..0fb716ed4 100644 --- a/desci-server/src/routes/v1/search.ts +++ b/desci-server/src/routes/v1/search.ts @@ -2,7 +2,6 @@ import { Router } from 'express'; import { multiQuery } from '../../controllers/search/multiQuery.js'; import { singleQuery } from '../../controllers/search/query.js'; -import { ensureUser } from '../../internal.js'; const router = Router(); diff --git a/desci-server/src/routes/v1/services.ts b/desci-server/src/routes/v1/services.ts index 0155fb7aa..40a9e8985 100644 --- a/desci-server/src/routes/v1/services.ts +++ b/desci-server/src/routes/v1/services.ts @@ -3,8 +3,8 @@ import multer from 'multer'; import { ephemeralThumbnail } from '../../controllers/proxy/ephemeralThumbnail.js'; import { orcidDid, orcidProfile } from '../../controllers/proxy/orcidProfile.js'; -import { ensureUser } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; +import { ensureUser } from '../../middleware/permissions.js'; const router = Router(); diff --git a/desci-server/src/scripts/backfill-annotations.ts b/desci-server/src/scripts/backfill-annotations.ts new file mode 100644 index 000000000..c0bb00fae --- /dev/null +++ b/desci-server/src/scripts/backfill-annotations.ts @@ -0,0 +1,31 @@ +import { prisma } from '../client.js'; + +const main = async () => { + const annotations = await prisma.annotation.findMany({ + where: { uuid: null }, + include: { attestation: { select: { nodeUuid: true } } }, + }); + const fields = await prisma.$transaction( + annotations.map(({ id, ...annotation }) => + prisma.annotation.upsert({ + where: { id }, + create: { + authorId: annotation.authorId, + body: annotation.body, + type: annotation.type, + nodeAttestationId: annotation.nodeAttestationId, + links: annotation.links, + visible: annotation.visible, + highlights: annotation.highlights, + uuid: annotation.uuid || annotation.attestation?.nodeUuid, + }, + update: { uuid: annotation.attestation?.nodeUuid }, + }), + ), + ); + console.log('Annotations fields updated', fields); +}; + +main() + .then((result) => console.log('Annotations backfilled', result)) + .catch((err) => console.log('Error running script ', err)); diff --git a/desci-server/src/scripts/dataRefDoctor.ts b/desci-server/src/scripts/dataRefDoctor.ts index 3b6490a29..62bfa6f6b 100644 --- a/desci-server/src/scripts/dataRefDoctor.ts +++ b/desci-server/src/scripts/dataRefDoctor.ts @@ -7,7 +7,7 @@ import { getSizeForCid } from '../services/ipfs.js'; import { getIndexedResearchObjects } from '../theGraph.js'; import { validateAndHealDataRefs, validateDataReferences } from '../utils/dataRefTools.js'; import { cleanupManifestUrl } from '../utils/manifest.js'; -import { hexToCid } from '../utils.js'; +import { ensureUuidEndsWithDot, hexToCid } from '../utils.js'; /* Usage Guidelines: @@ -18,6 +18,7 @@ Usage Guidelines: - START and END are optional flags, if set, it will only process nodes within the range. - MARK_EXTERNALS is an optional flag, if true, it will mark external refs as external, downside is that it can take significantly longer to process, also size diff checking disabled when marking externals. - TX_HASH is an optional param, used for fixing node version of a specific published node version. (Edgecase of multiple publishes with same manifestCid) +- COMMIT_ID is an optional param, used for fixing node version of a specific published node version. - USER_EMAIL is only required for the fillPublic operation - WORKING_TREE_URL is only required for the fillPublic operation, useful if a node is known to contain external cids, it can cut down the backfill time significantly for dags with external cids. @@ -38,7 +39,7 @@ healAll: OPERATION=healAll PUBLIC_REFS=true MARK_EXTERNALS=true npm run scr const logger = parentLogger.child({ module: 'SCRIPTS::dataRefDoctor' }); main(); -function main() { +async function main() { const { operation, nodeUuid, @@ -48,40 +49,42 @@ function main() { end, markExternals, txHash, + commitId, userEmail, workingTreeUrl, newNodeUuid, } = getOperationEnvs(); const startIterator = isNaN(start as any) ? undefined : parseInt(start); const endIterator = isNaN(end as any) ? undefined : parseInt(end); - switch (operation) { case 'validate': if (!nodeUuid && !manifestCid) return logger.error('Missing NODE_UUID or MANIFEST_CID'); - validateDataReferences({ nodeUuid, manifestCid, publicRefs, markExternals, txHash }); + await validateDataReferences({ nodeUuid, manifestCid, publicRefs, markExternals, txHash, commitId }); break; case 'heal': if (!nodeUuid && !manifestCid) return logger.error('Missing NODE_UUID or MANIFEST_CID'); - validateAndHealDataRefs({ nodeUuid, manifestCid, publicRefs, markExternals, txHash }); + await validateAndHealDataRefs({ nodeUuid, manifestCid, publicRefs, markExternals, txHash, commitId }); break; case 'validateAll': - dataRefDoctor(false, publicRefs, startIterator, endIterator, markExternals); + await dataRefDoctor({ heal: false, publicRefs, start: startIterator, end: endIterator, markExternals }); break; case 'healAll': - dataRefDoctor(true, publicRefs, startIterator, endIterator, markExternals); + await dataRefDoctor({ heal: true, publicRefs, start: startIterator, end: endIterator, markExternals }); break; case 'fillPublic': if (!nodeUuid && !userEmail) return logger.error('Missing NODE_UUID or USER_EMAIL'); - fillPublic(nodeUuid, userEmail, workingTreeUrl); + await fillPublic(nodeUuid, userEmail, workingTreeUrl); break; case 'clonePrivateNode': if (!nodeUuid && !newNodeUuid) return logger.error('Missing NODE_UUID or NEW_NODE_UUID'); - clonePrivateNode(nodeUuid, newNodeUuid); + await clonePrivateNode(nodeUuid, newNodeUuid); break; default: logger.error('Invalid operation, valid operations include: validate, heal, validateAll, healAll'); return; } + logger.info('DataRefDr has finished running'); + process.exit(0); } function getOperationEnvs() { @@ -95,19 +98,22 @@ function getOperationEnvs() { end: process.env.END, markExternals: process.env.MARK_EXTERNALS?.toLowerCase() === 'true' ? true : false, txHash: process.env.TX_HASH || null, + commitId: process.env.COMMIT_ID || null, workingTreeUrl: process.env.WORKING_TREE_URL || null, userEmail: process.env.USER_EMAIL || null, }; } +type DataRefDoctorArgs = { + heal: boolean; + publicRefs: boolean; + start?: number; + end?: number; + markExternals?: boolean; +}; + //todo: add public handling -async function dataRefDoctor( - heal: boolean, - publicRefs: boolean, - start?: number, - end?: number, - markExternals?: boolean, -) { +async function dataRefDoctor({ heal, publicRefs, start, end, markExternals }: DataRefDoctorArgs) { const nodes = await prisma.node.findMany({ orderBy: { id: 'asc', @@ -133,11 +139,14 @@ async function dataRefDoctor( `[DataRefDoctor]Processing node: ${nodes[i].id}, found versions indexed: ${totalVersionsIndexed}, for nodeUuid: ${node.uuid}`, ); for (let nodeVersIdx = 0; nodeVersIdx < totalVersionsIndexed; nodeVersIdx++) { - logger.info( - `[DataRefDoctor]Processing indexed version: ${nodeVersIdx}, with txHash: ${indexedNode.versions[nodeVersIdx]?.id}`, - ); const hexCid = indexedNode.versions[nodeVersIdx]?.cid || indexedNode.recentCid; const txHash = indexedNode.versions[nodeVersIdx]?.id; + const commitId = indexedNode.versions[nodeVersIdx]?.commitId; + const publishIdentifier = commitId || txHash; + + logger.info( + `[DataRefDoctor]Processing indexed version: ${nodeVersIdx}, with publishIdentifier: ${publishIdentifier}`, + ); const manifestCid = hexToCid(hexCid); if (heal) { await validateAndHealDataRefs({ @@ -146,9 +155,19 @@ async function dataRefDoctor( publicRefs: true, markExternals, txHash, + commitId, + includeManifestRef: true, }); } else { - validateDataReferences({ nodeUuid: node.uuid, manifestCid, publicRefs: true, markExternals, txHash }); + validateDataReferences({ + nodeUuid: node.uuid, + manifestCid, + publicRefs: true, + markExternals, + txHash, + commitId, + includeManifestRef: true, + }); } } } @@ -159,6 +178,7 @@ async function dataRefDoctor( manifestCid: node.manifestUrl, publicRefs: false, markExternals, + includeManifestRef: true, }); } else { await validateDataReferences({ @@ -166,6 +186,7 @@ async function dataRefDoctor( manifestCid: node.manifestUrl, publicRefs: false, markExternals, + includeManifestRef: true, }); } } @@ -179,10 +200,15 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: const user = await prisma.user.findUnique({ where: { email: userEmail } }); if (!user) return logger.error(`[FillPublic] Failed to find user with email: ${userEmail}`); - if (!nodeUuid.endsWith('.')) nodeUuid += '.'; + nodeUuid = ensureUuidEndsWithDot(nodeUuid); const { researchObjects } = await getIndexedResearchObjects([nodeUuid]); - if (!researchObjects.length) - logger.error(`[FillPublic] Failed to resolve any public nodes with the uuid: ${nodeUuid}`); + if (!researchObjects.length) { + logger.error( + { nodeUuid, researchObjects }, + `[FillPublic] Failed to resolve any published nodes with the uuid: ${nodeUuid}, aborting script`, + ); + return; + } const indexedNode = researchObjects[0]; const latestHexCid = indexedNode.recentCid; @@ -190,11 +216,13 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: const manifestUrl = cleanupManifestUrl(latestManifestCid); const latestManifest = await (await axios.get(manifestUrl)).data; - if (!latestManifest) - return logger.error( + if (!latestManifest) { + logger.error( { manifestUrl, latestManifestCid }, - `[FillPublic] Failed to retrieve manifest from ipfs cid: ${latestManifestCid}`, + `[FillPublic] Failed to retrieve manifest from ipfs cid: ${latestManifestCid}, aborting script`, ); + return; + } const title = '[IMPORTED NODE]' + latestManifest.title || 'Imported Node'; let node = await prisma.node.findUnique({ where: { uuid: nodeUuid } }); @@ -214,18 +242,26 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: const totalVersionsIndexed = indexedNode.versions.length || 0; try { for (let nodeVersIdx = 0; nodeVersIdx < totalVersionsIndexed; nodeVersIdx++) { - logger.info( - `[DataRefDoctor]Processing indexed version: ${nodeVersIdx}, with txHash: ${indexedNode.versions[nodeVersIdx]?.id}`, - ); const hexCid = indexedNode.versions[nodeVersIdx]?.cid || indexedNode.recentCid; const txHash = indexedNode.versions[nodeVersIdx]?.id; + const commitId = indexedNode.versions[nodeVersIdx]?.commitId; + const commitIdOrTxHash = commitId || txHash; + + logger.info( + `[DataRefDoctor]Processing indexed version: ${nodeVersIdx}, with ${commitId ? 'commitId:' : 'txHash'}: ${commitIdOrTxHash}`, + ); const manifestCid = hexToCid(hexCid); + const nodeVersionPublishIdentifiers = { + ...(txHash && { transactionId: txHash }), + ...(commitId && { commitId }), + }; + const nodeVersion = await prisma.nodeVersion.create({ data: { nodeId: node.id, manifestUrl: manifestCid, - transactionId: txHash, + ...nodeVersionPublishIdentifiers, }, }); @@ -242,7 +278,7 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: }; logger.info( { manifestEntry }, - `[DataRefDoctor] Manifest entry being created for indexed version ${nodeVersIdx}, with txHash: ${indexedNode.versions[nodeVersIdx]?.id}`, + `[DataRefDoctor] Manifest entry being created for indexed version ${nodeVersIdx}, with publishIdentifier: ${commitIdOrTxHash}`, ); await prisma.publicDataReference.create({ data: manifestEntry }); @@ -252,10 +288,11 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: manifestCid, publicRefs: true, txHash, + commitId, workingTreeUrl, }); logger.info( - `[DataRefDoctor]Successfully processed indexed node v: ${nodeVersIdx}, with txHash: ${indexedNode.versions[nodeVersIdx]?.id}, under user: ${user.email}`, + `[DataRefDoctor]Successfully processed indexed node v: ${nodeVersIdx}, with publishIdentifier: ${commitIdOrTxHash}, under user: ${user.email}`, ); } logger.info(`[FillPublic] Successfully backfilled data refs for public node: ${nodeUuid}`); @@ -272,7 +309,7 @@ async function fillPublic(nodeUuid: string, userEmail: string, workingTreeUrl?: totalVersionsIndexed, indexedNode, }, - `[FillPublic] Failed to backfill data refs for public node: ${nodeUuid}, error`, + `[FillPublic] Failed to backfill data refs for public node: ${nodeUuid}`, ); } } diff --git a/desci-server/src/scripts/invalidate-redis-cache.ts b/desci-server/src/scripts/invalidate-redis-cache.ts index b9a303501..07b3a3225 100644 --- a/desci-server/src/scripts/invalidate-redis-cache.ts +++ b/desci-server/src/scripts/invalidate-redis-cache.ts @@ -2,13 +2,13 @@ import { isNodeRoot } from '@desci-labs/desci-models'; import axios from 'axios'; import { logger as parentLogger } from '../logger.js'; -import redisClient from '../redisClient.js'; +import { redisClient } from '../redisClient.js'; import { getIndexedResearchObjects } from '../theGraph.js'; import { cleanupManifestUrl } from '../utils/manifest.js'; import { hexToCid } from '../utils.js'; const logger = parentLogger.child({ module: 'SCRIPTS::invalidateRedisKeys' }); -/* +/* Usage Examples: invalidateByUuid: OPERATION=invalidateByUuid NODE_UUID=noDeUuiD. npm run script:invalidate-redis-cache diff --git a/desci-server/src/server.ts b/desci-server/src/server.ts index 5b16b2d6e..f1fd06ba8 100644 --- a/desci-server/src/server.ts +++ b/desci-server/src/server.ts @@ -2,13 +2,10 @@ import 'dotenv/config'; import 'reflect-metadata'; import * as child from 'child_process'; -// import fs from 'fs'; import type { Server as HttpServer } from 'http'; -// import path from 'path'; import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; -// import * as Tracing from '@sentry/tracing'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import express from 'express'; @@ -16,23 +13,28 @@ import type { Express, Request } from 'express'; import helmet from 'helmet'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { pinoHttp } from 'pino-http'; +import { Server as SocketIOServer } from 'socket.io'; import { v4 } from 'uuid'; -import { prisma } from './client.js'; -// eslint-disable-next-line import/order -import routes from './routes/index.js'; import { orcidConnect } from './controllers/auth/orcid.js'; import { orcidCheck } from './controllers/auth/orcidNext.js'; -// import SocketServer from './wsServer.js'; -import { NotFoundError, RequestWithUser, extractAuthToken, extractUserFromToken } from './internal.js'; -import { als, logger } from './logger.js'; +import { NotFoundError } from './core/ApiError.js'; +import { als, logger as parentLogger } from './logger.js'; +import { RequestWithUser } from './middleware/authorisation.js'; import { ensureUserIfPresent } from './middleware/ensureUserIfPresent.js'; import { errorHandler } from './middleware/errorHandler.js'; +import { extractAuthToken, extractUserFromToken } from './middleware/permissions.js'; +import routes from './routes/index.js'; +import { initializeWebSocketServer } from './websocketServer.js'; import { SubmissionQueueJob } from './workers/doiSubmissionQueue.js'; import { runWorkerUntilStopped } from './workers/publish.js'; // const __dirname = path.dirname(__filename); +const logger = parentLogger.child({ + module: 'server.ts', +}); + const ENABLE_TELEMETRY = process.env.NODE_ENV === 'production'; const IS_DEV = !ENABLE_TELEMETRY; @@ -66,6 +68,8 @@ class AppServer { server: HttpServer; port: number; + private _io: SocketIOServer | null = null; + constructor() { this.app = express(); @@ -145,6 +149,7 @@ class AppServer { this.#isReady = true; this.#readyResolvers.forEach((resolve) => resolve(true)); console.log(`Server running on port ${this.port}`); + if (process.env.NODE_ENV !== 'test') this.#attachWebsockets(); }); // start jobs @@ -165,6 +170,32 @@ class AppServer { }); } + async #attachWebsockets() { + const maxRetries = 3; + let retries = 0; + const retryTimeout = 5000; + + while (retries < maxRetries) { + try { + this._io = await initializeWebSocketServer(this.server); + this.app.set('io', this._io); + logger.info('WebSocket server initialized successfully'); + return; + } catch (error) { + retries++; + logger.error({ error, retries }, 'Failed to initialize WebSocket server, retrying...'); + await new Promise((resolve) => setTimeout(resolve, retryTimeout)); + } + } + + logger.error('Failed to initialize WebSocket server after maximum retries'); + } + + // websockets getter + get io(): SocketIOServer | null { + return this._io; + } + #attachRouteHandlers() { this.app.get('/version', (req, res) => { const revision = child.execSync('git rev-parse HEAD').toString().trim(); diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index 0e2e0c499..ebaf6f2a7 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -11,22 +11,20 @@ import { AttestationVersionNotFoundError, ClaimNotFoundError, CommunityNotFoundError, - CommunityRadarNode, DuplicateClaimError, DuplicateDataError, DuplicateReactionError, DuplicateVerificationError, NoAccessError, - NotFoundError, VerificationError, VerificationNotFoundError, - asyncMap, - ensureUuidEndsWithDot, - logger as parentLogger, - uuidPathRegex, -} from '../internal.js'; -import { communityService } from '../internal.js'; +} from '../core/communities/error.js'; +import { logger as parentLogger } from '../logger.js'; +import { uuidPathRegex } from '../routes/v1/attestations/schema.js'; import { AttestationClaimedEmailHtml } from '../templates/emails/utils/emailRenderer.js'; +import { asyncMap, ensureUuidEndsWithDot } from '../utils.js'; + +import { CommunityRadarNode, communityService } from './Communities.js'; sgMail.setApiKey(process.env.SENDGRID_API_KEY); @@ -1108,13 +1106,13 @@ export class AttestationService { to: member.user.email, from: 'no-reply@desci.com', subject: `[nodes.desci.com] ${versionedAttestation.name} claimed on DPID://${nodeDpid}/v${nodeVersion + 1}`, - text: `${user.name} just claimed ${versionedAttestation.name} on ${process.env.DAPP_URL}/dpid/${nodeDpid}/v${nodeVersion + 1}?claim=${nodeAttestation.id}`, + text: `${user.name} just claimed ${versionedAttestation.name} on ${process.env.DAPP_URL}/dpid/${nodeDpid}/v${nodeVersion + 1}/attestations/${nodeAttestation.id}`, html: AttestationClaimedEmailHtml({ dpid: nodeDpid, attestationName: versionedAttestation.name, communityName: versionedAttestation.name, userName: user.name, - dpidPath: `${process.env.DAPP_URL}/dpid/${nodeDpid}/v${nodeVersion + 1}?claim=${nodeAttestation.id}`, + dpidPath: `${process.env.DAPP_URL}/dpid/${nodeDpid}/v${nodeVersion + 1}/attestations/${nodeAttestation.id}`, }), })); diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 6e9a467aa..6b1fda9af 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -2,8 +2,10 @@ import { Attestation, CommunityMembershipRole, NodeAttestation, NodeFeedItem, Pr import _, { includes } from 'lodash'; import { prisma } from '../client.js'; -import { DuplicateDataError, logger } from '../internal.js'; -import { attestationService } from '../internal.js'; +import { DuplicateDataError } from '../core/communities/error.js'; +import { logger } from '../logger.js'; + +import { attestationService } from './Attestation.js'; export type CommunityRadarNode = NodeAttestation & { annotations: number; reactions: number; verifications: number }; export class CommunityService { @@ -27,13 +29,48 @@ export class CommunityService { orderBy: { role: 'asc' }, }, CommunityEntryAttestation: { - select: { id: true, attestationVersion: { select: { name: true, image_url: true } } }, + select: { id: true, attestationVersion: { select: { id: true, name: true, image_url: true } } }, orderBy: { createdAt: 'asc' }, }, }, }); } + async adminGetAttestations(where?: Prisma.AttestationWhereInput) { + return prisma.attestation.findMany({ + orderBy: { createdAt: 'asc' }, + where, + include: { + community: { select: { name: true } }, + AttestationVersion: { + select: { name: true, description: true, image_url: true }, + orderBy: { createdAt: 'desc' }, + }, + ...(where?.communityId + ? { + CommunityEntryAttestation: { + where: { desciCommunityId: where.communityId }, + }, + } + : { CommunityEntryAttestation: { select: { desciCommunityId: true, id: true } } }), + }, + }); + } + + async getEntryAttestations(where?: Prisma.CommunityEntryAttestationWhereInput) { + return prisma.communityEntryAttestation.findMany({ + orderBy: { createdAt: 'asc' }, + where, + include: { + attestation: { select: { protected: true, community: { select: { name: true } } } }, + // desciCommunity: { select: { name: true } }, + attestationVersion: { + select: { id: true, attestationId: true, name: true, image_url: true, description: true }, + }, + }, + }); + } + async getAllCommunities() { return prisma.desciCommunity.findMany({ select: { diff --git a/desci-server/src/services/Doi.ts b/desci-server/src/services/Doi.ts index cf4994d51..bbb1e0b5f 100644 --- a/desci-server/src/services/Doi.ts +++ b/desci-server/src/services/Doi.ts @@ -1,5 +1,5 @@ import { PdfComponent, ResearchObjectComponentType, ResearchObjectV1 } from '@desci-labs/desci-models'; -import { DoiStatus, DoiSubmissionQueue, Prisma, PrismaClient } from '@prisma/client'; +import { DoiStatus, DoiSubmissionQueue, NodeVersion, Prisma, PrismaClient } from '@prisma/client'; import { v4 } from 'uuid'; import { @@ -108,7 +108,17 @@ export class DoiService { // check if node has claimed doi already // check with dpid instead or dpid/path/to/manuscript or dpid/path/to/file - await this.assertIsFirstDoi(latestManifest.dpid.id); + const node = await this.dbClient.node.findFirst({ + where: { uuid: ensureUuidEndsWithDot(nodeUuid) }, + select: { dpidAlias: true }, + }); + logger.trace({ latestManifest, node }, 'Debug dpid'); + const dpid = latestManifest?.dpid?.id || node?.dpidAlias.toString(); + if (!dpid) { + logger.error({ dpid, uuid }, 'checkMintability::No DPID found'); + throw new ForbiddenMintError('Node dpid not found'); + } + await this.assertIsFirstDoi(dpid); // extract manuscripts const manuscripts = latestManifest.components.filter( @@ -117,15 +127,13 @@ export class DoiService { component.name.endsWith('.pdf') || component.payload?.path?.endsWith('.pdf'), ) as PdfComponent[]; - logger.info(manuscripts, 'MANUSCRIPTS'); if (manuscripts.length > 0) { const existingDois = manuscripts.filter((doc) => doc.payload?.doi && doc.payload.doi.length > 0); - // await this.extractManuscriptDoi(manuscripts); - logger.info(existingDois, 'Existing DOI'); // does manuscript(s) already have a DOI if (existingDois.length) { + logger.trace({ existingDois }, 'Existing DOI'); // Validate node has claimed all necessary attestations await this.assertHasValidatedAttestations(uuid); } @@ -137,7 +145,20 @@ export class DoiService { // validate title, abstract and contributors this.assertValidManifest(latestManifest); - return { dpid: latestManifest.dpid.id, uuid, manifest: latestManifest, researchObject }; + return { dpid, uuid, manifest: latestManifest, researchObject }; + } + + async getLastPublishedDate(uuid: string) { + // const node = await this.dbClient.node.findFirst({ where: { uuid } }); + const publishedVersions = await this.dbClient.nodeVersion.findFirst({ + select: { createdAt: true }, + where: { node: { uuid }, OR: [{ commitId: { not: null } }, { transactionId: { not: null } }] }, + orderBy: { createdAt: 'desc' }, + }); + logger.trace({ publishedVersions }, 'getLastPublishedDate'); + const time = publishedVersions.createdAt; + logger.trace({ time }, 'getLastPublishedDate'); + return new Date(time); } async mintDoi(nodeUuid: string) { @@ -146,14 +167,26 @@ export class DoiService { const doiSuffix = v4().substring(0, 8); const doi = `${DOI_PREFIX}/${doiSuffix}`; - const latestVersion = researchObject.versions[researchObject.versions.length - 1]; - const publicationDate = new Date(parseInt(latestVersion.time) * 1000).toLocaleDateString().replaceAll('/', '-'); + const latestVersionTimestamp = researchObject.versions[researchObject.versions.length - 1]?.time; + const publicationDate = latestVersionTimestamp + ? new Date(parseInt(latestVersionTimestamp) * 1000).toLocaleDateString().replaceAll('/', '-') + : (await this.getLastPublishedDate(uuid)).toLocaleDateString().replaceAll('/', '-'); + logger.trace( + { + latestVersionTimestamp: new Date(parseInt(latestVersionTimestamp) * 1000) + .toLocaleDateString() + .replaceAll('/', '-'), + publicationDate, + }, + 'latestVersionTimestamp', + ); const [month, day, year] = publicationDate.split('-'); const metadataResponse = await crossRefClient.registerDoi({ manifest, doi, + dpid, publicationDate: { day, month, year }, }); diff --git a/desci-server/src/services/ElasticSearchService.ts b/desci-server/src/services/ElasticSearchService.ts index 0fb2b15c6..72179f358 100644 --- a/desci-server/src/services/ElasticSearchService.ts +++ b/desci-server/src/services/ElasticSearchService.ts @@ -146,46 +146,129 @@ export function createFunctionScoreQuery(query: QueryDslQueryContainer, entity: } export function createAutocompleteFunctionScoreQuery(query: string): QueryDslQueryContainer { - const functions: QueryDslFunctionScoreContainer[] = [ - // Citation count + // Use these as tie breakers, low weights/factors compared to the text matches + const boostFunctions: QueryDslFunctionScoreContainer[] = [ { filter: { range: { cited_by_count: { gte: 1 } } }, field_value_factor: { field: 'cited_by_count', - factor: 0.01, + factor: 0.001, modifier: 'log1p', }, - weight: 10, + weight: 1, }, - // Works count { filter: { range: { works_count: { gte: 1 } } }, field_value_factor: { field: 'works_count', - factor: 0.005, + factor: 0.0005, modifier: 'log1p', }, - weight: 5, + weight: 0.5, }, ]; const shouldClauses: QueryDslQueryContainer[] = [ - // Exact match on keyword fields + // Exact keyword matches (highest priority) { - multi_match: { - query: query, - fields: [ - 'title.keyword^10', - 'primary_id.keyword^10', - 'entity_type.keyword^5', - 'publisher.keyword^5', - 'issn.keyword^5', - 'id.keyword^5', + bool: { + should: [ + { + term: { + 'title.keyword': { + value: query.toLowerCase(), + boost: 100, + }, + }, + }, + { + term: { + 'primary_id.keyword': { + value: query.toLowerCase(), + boost: 100, + }, + }, + }, + { + term: { + 'id.keyword': { + value: query.toLowerCase(), + boost: 100, + }, + }, + }, ], - type: 'best_fields', + minimum_should_match: 1, }, }, - // match on text fields + // 80% threshold matches (high-medium priority) + { + bool: { + should: [ + { + match: { + title: { + query: query, + minimum_should_match: '80%', + boost: 75, + }, + }, + }, + { + match: { + primary_id: { + query: query, + minimum_should_match: '80%', + boost: 75, + }, + }, + }, + { + match: { + 'institution_data.display_name': { + query: query, + minimum_should_match: '80%', + boost: 75, + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + // Prefix matches (medium priority) + { + bool: { + should: [ + { + prefix: { + 'title.keyword': { + value: query.toLowerCase(), + boost: 50, + }, + }, + }, + { + prefix: { + 'primary_id.keyword': { + value: query.toLowerCase(), + boost: 50, + }, + }, + }, + { + prefix: { + 'id.keyword': { + value: query.toLowerCase(), + boost: 50, + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + // Text matches (lower priority) { multi_match: { query: query, @@ -196,6 +279,8 @@ export function createAutocompleteFunctionScoreQuery(query: string): QueryDslQue 'subfield_display_name^2', 'institution_data.display_name^2', ], + type: 'phrase_prefix', + boost: 10, }, }, ]; @@ -207,9 +292,10 @@ export function createAutocompleteFunctionScoreQuery(query: string): QueryDslQue const functionScoreQuery: QueryDslFunctionScoreQuery = { query: { bool: boolQuery }, - functions, - boost_mode: 'multiply' as QueryDslFunctionBoostMode, - score_mode: 'sum' as QueryDslFunctionScoreMode, + functions: boostFunctions, + boost_mode: 'sum' as QueryDslFunctionBoostMode, + score_mode: 'multiply' as QueryDslFunctionScoreMode, + min_score: 0.1, }; return { function_score: functionScoreQuery }; diff --git a/desci-server/src/services/NotificationService.ts b/desci-server/src/services/NotificationService.ts new file mode 100644 index 000000000..10044d3af --- /dev/null +++ b/desci-server/src/services/NotificationService.ts @@ -0,0 +1,280 @@ +import { NotificationType, Prisma, User, UserNotifications, Node } from '@prisma/client'; +import { z } from 'zod'; + +import { prisma } from '../client.js'; +import { CreateNotificationSchema } from '../controllers/notifications/create.js'; +import { GetNotificationsQuerySchema, PaginatedResponse } from '../controllers/notifications/index.js'; +import { logger as parentLogger } from '../logger.js'; +import { server } from '../server.js'; +import { emitWebsocketEvent, WebSocketEventType } from '../utils/websocketHelpers.js'; + +type GetNotificationsQuery = z.infer; +export type CreateNotificationData = z.infer; + +const logger = parentLogger.child({ + module: 'UserNotifications::NotificationService', +}); + +export type NotificationSettings = Partial>; + +export type NotificationUpdateData = { + dismissed?: boolean; + // seen?: boolean; // future +}; + +export type CommentPayload = { + type: 'COMMENTS'; + nodeUuid: string; + annotationId: number; +}; + +export type PublishPayload = { + type: 'PUBLISH'; + nodeUuid: string; + dpid: string; +}; + +export const getUserNotifications = async ( + userId: number, + query: GetNotificationsQuery, +): Promise> => { + const { page, perPage, dismissed = false } = query; + const skip = (page - 1) * perPage; + const whereClause = { + userId, + dismissed, + }; + + const [notifications, totalItems] = await Promise.all([ + prisma.userNotifications.findMany({ + where: whereClause, + skip, + take: perPage, + orderBy: { createdAt: 'desc' }, + }), + prisma.userNotifications.count({ where: whereClause }), + ]); + + const totalPages = Math.ceil(totalItems / perPage); + + return { + data: notifications, + pagination: { + currentPage: page, + totalPages, + totalItems, + }, + }; +}; + +export const createUserNotification = async ( + data: CreateNotificationData, + options?: { throwOnDisabled?: boolean }, +): Promise => { + logger.info({ data }, 'Creating user notification'); + + const settings = await getNotificationSettings(data.userId); + + if (!shouldSendNotification(settings, data.type)) { + logger.warn({ userId: data.userId, type: data.type }, 'Notification creation blocked by user settings'); + if (options?.throwOnDisabled) throw new Error('Notification type is disabled for this user'); + return null; + } + + if (data.nodeUuid) { + // Validate node belongs to user + const node = await prisma.node.findUnique({ + where: { uuid: data.nodeUuid }, + select: { ownerId: true }, + }); + + if (!node) { + logger.warn({ nodeUuid: data.nodeUuid }, 'Node not found'); + throw new Error('Node not found'); + } + + if (node.ownerId !== data.userId) { + logger.warn({ nodeUuid: data.nodeUuid, userId: data.userId }, 'Node does not belong to the user'); + throw new Error('Node does not belong to the user'); + } + } + + if (!Object.values(NotificationType).includes(data.type as NotificationType)) { + // Validates valid notification type + logger.warn({ type: data.type }, 'Invalid notification type'); + throw new Error('Invalid notification type'); + } + + const notificationData: Prisma.UserNotificationsCreateInput = { + user: { connect: { id: data.userId } }, + type: data.type as NotificationType, + title: data.title, + message: data.message, + dismissed: false, + node: data.nodeUuid ? { connect: { uuid: data.nodeUuid } } : undefined, + payload: data.payload ? (data.payload as Prisma.JsonObject) : undefined, + }; + + const notification = await prisma.userNotifications.create({ + data: notificationData, + }); + + logger.info({ notificationId: notification.id }, 'User notification created successfully'); + + // Emit websocket push notification + emitWebsocketEvent(data.userId, { type: WebSocketEventType.NOTIFICATION, data: 'invalidate-cache' }); + + return notification; +}; + +export const updateUserNotification = async ( + notificationId: number, + userId: number, + updateData: NotificationUpdateData, +): Promise => { + logger.info({ notificationId, userId, updateData }, 'Updating user notification'); + + const notification = await prisma.userNotifications.findUnique({ + where: { id: notificationId }, + }); + + if (!notification) { + logger.warn({ notificationId }, 'Notification not found'); + throw new Error('Notification not found'); + } + + if (notification.userId !== userId) { + logger.warn({ notificationId, userId }, 'Notification does not belong to the user'); + throw new Error('Notification does not belong to the user'); + } + + const updatedNotification = await prisma.userNotifications.update({ + where: { id: notificationId }, + data: updateData, + }); + + logger.info({ notificationId: updatedNotification.id }, 'User notification updated successfully'); + return updatedNotification; +}; + +export const batchUpdateUserNotifications = async ( + notificationIds: number[], + userId: number, + updateData: NotificationUpdateData, +): Promise => { + logger.info({ notificationIds, userId, updateData }, 'Batch updating user notifications'); + + const result = await prisma.userNotifications.updateMany({ + where: { + id: { in: notificationIds }, + userId: userId, + }, + data: updateData, + }); + + logger.info({ userId, count: result.count }, 'User notifications batch updated successfully'); + return result.count; +}; + +export const updateNotificationSettings = async ( + userId: number, + newSettings: NotificationSettings, +): Promise>> => { + logger.info({ userId, newSettings }, 'Updating user notification settings'); + + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { notificationSettings: true }, + }); + + const currentSettings = (user?.notificationSettings as NotificationSettings) || {}; + const mergedSettings = { ...currentSettings, ...newSettings }; + + const updatedUser = await prisma.user.update({ + where: { id: userId }, + data: { + notificationSettings: mergedSettings as Prisma.JsonObject, + }, + }); + + logger.info({ userId, mergedSettings }, 'User notification settings updated successfully'); + return mergedSettings; +}; + +/* + ** A JSON object stored on the User model, if is set to false, the user will not receive notifications of that type, + ** otherwise, they will receive notifications of that type. Note: Undefined types will default to true. + */ +export const getNotificationSettings = async (userId: number): Promise => { + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { notificationSettings: true }, + }); + + return (user?.notificationSettings as NotificationSettings) || {}; +}; + +export const shouldSendNotification = (settings: NotificationSettings, type: NotificationType): boolean => { + return settings[type] !== false; +}; + +export const emitNotificationForAnnotation = async (annotationId: number) => { + const annotation = await prisma.annotation.findUnique({ + where: { id: annotationId }, + include: { + author: true, + node: { + include: { owner: true }, + }, + attestation: { + include: { + node: { + include: { owner: true }, + }, + }, + }, + }, + }); + + if (!annotation) { + logger.warn({ annotationId }, 'Annotation not found'); + return; + } + + const annotationAuthor = annotation.author; + const annotationAuthorName = annotationAuthor?.name || 'A user'; + const node = annotation.node || annotation.attestation?.node; + const nodeOwner = node?.owner; + + if (!nodeOwner) { + logger.warn({ annotationId }, 'Linked owner not found on annotation'); + return; + } + + const dotlessUuid = node.uuid.replace(/\./g, ''); + + const notificationData: CreateNotificationData = { + userId: nodeOwner.id, + type: NotificationType.COMMENTS, + title: `${annotationAuthorName} commented on your research object`, + message: `Your research object titled ${node.title}, has received a new comment.`, // TODO:: Ideally deserialize some of the message body from the annotation and show a truncated snippet + nodeUuid: node.uuid, + payload: { type: NotificationType.COMMENTS, nodeUuid: dotlessUuid, annotationId } as CommentPayload, + }; + + await createUserNotification(notificationData); +}; +// +export const emitNotificationOnPublish = async (node: Node, user: User, dpid: string) => { + const dotlessUuid = node.uuid.replace(/\./g, ''); + const notificationData: CreateNotificationData = { + userId: user.id, + type: NotificationType.PUBLISH, + title: `Your research object has been published!`, + message: `Your research object titled "${node.title}" has been published and is now available for public access.`, + nodeUuid: node.uuid, + payload: { type: NotificationType.PUBLISH, nodeUuid: dotlessUuid, dpid } as PublishPayload, + }; + + await createUserNotification(notificationData); +}; diff --git a/desci-server/src/services/PublishPackage.ts b/desci-server/src/services/PublishPackage.ts index 05ccd693a..6a244ecb9 100644 --- a/desci-server/src/services/PublishPackage.ts +++ b/desci-server/src/services/PublishPackage.ts @@ -3,9 +3,9 @@ import { Node } from '@prisma/client'; import axios from 'axios'; import { prisma } from '../client.js'; -import { cachedGetDpidFromManifest } from '../internal.js'; import { logger as parentLogger } from '../logger.js'; import { getIndexedResearchObjects } from '../theGraph.js'; +import { cachedGetDpidFromManifest } from '../utils/manifest.js'; import { ensureUuidEndsWithDot, hexToCid, toKebabCase } from '../utils.js'; import { attestationService } from './Attestation.js'; @@ -71,9 +71,10 @@ class PublishPackageService { let nodeUuid = ensureUuidEndsWithDot(node.uuid); nodeUuid = nodeUuid.slice(0, -1); // const paddedTimestamp = unixTimestamp.padEnd(13, '0'); + const publishTime = demoMode ? Date.now().toString().slice(0, 10) - : await publishServices.retrieveBlockTimeByManifestCid(nodeUuid, usedManifestCid); + : (await publishServices.retrieveBlockTimeByManifestCid(nodeUuid, usedManifestCid)).slice(0, 10); const publishDate = PublishPackageService.convertUnixTimestampToDate(publishTime); const authors = manifest.authors?.map((author) => author.name); diff --git a/desci-server/src/services/PublishServices.ts b/desci-server/src/services/PublishServices.ts index 5749e13c8..e44baf4e4 100644 --- a/desci-server/src/services/PublishServices.ts +++ b/desci-server/src/services/PublishServices.ts @@ -2,11 +2,13 @@ import { DataType, EmailType, Node, NodeContribution, User } from '@prisma/clien import sgMail from '@sendgrid/mail'; import { prisma } from '../client.js'; -import { attestationService, ensureUuidEndsWithDot, getNodeVersion, hexToCid } from '../internal.js'; +import { getNodeVersion } from '../controllers/communities/util.js'; import { logger as parentLogger } from '../logger.js'; import { SubmissionPackageEmailHtml } from '../templates/emails/utils/emailRenderer.js'; import { getIndexedResearchObjects, getTimeForTxOrCommits } from '../theGraph.js'; +import { ensureUuidEndsWithDot } from '../utils.js'; +import { attestationService } from './Attestation.js'; import { contributorService } from './Contributors.js'; import { getLatestManifestFromNode } from './manifestRepo.js'; diff --git a/desci-server/src/services/crossRef/client.ts b/desci-server/src/services/crossRef/client.ts index 4c2a78d87..ebcc142ed 100644 --- a/desci-server/src/services/crossRef/client.ts +++ b/desci-server/src/services/crossRef/client.ts @@ -177,6 +177,7 @@ class CrossRefClient { async registerDoi(query: { manifest: ResearchObjectV1; doi: string; + dpid: string; publicationDate: PublicationDate; }): Promise { const contributors = await asyncMap(query.manifest.authors ?? [], async (author, index) => { @@ -210,9 +211,9 @@ class CrossRefClient { _: { batchId, timestamp: Date.now(), - dpid: query.manifest.dpid.id, + dpid: query.dpid, doi: query.doi, - doiResource: `${process.env.DPID_URL_OVERRIDE}/${query.manifest.dpid.id}`, + doiResource: `${process.env.DPID_URL_OVERRIDE}/${query.dpid}`, title: query.manifest.title, registrant: 'DeSci Labs AG', contributors, diff --git a/desci-server/src/services/nodeManager.ts b/desci-server/src/services/nodeManager.ts index 1436da574..8970a3d44 100644 --- a/desci-server/src/services/nodeManager.ts +++ b/desci-server/src/services/nodeManager.ts @@ -5,7 +5,6 @@ import axios from 'axios'; import { prisma } from '../client.js'; import { MEDIA_SERVER_API_KEY, MEDIA_SERVER_API_URL, PUBLIC_IPFS_PATH } from '../config/index.js'; -import { ForbiddenError, NodeUuid, NotFoundError } from '../internal.js'; import { logger as parentLogger } from '../logger.js'; import { getFromCache } from '../redisClient.js'; import { getIndexedResearchObjects } from '../theGraph.js'; @@ -15,6 +14,7 @@ import { cleanupManifestUrl, transformManifestWithHistory } from '../utils/manif import { hexToCid, randomUUID64, asyncMap, ensureUuidEndsWithDot } from '../utils.js'; import { addBufferToIpfs, downloadFilesAndMakeManifest, getSizeForCid, resolveIpfsData } from './ipfs.js'; +import { NodeUuid } from './manifestRepo.js'; import repoService from './repoService.js'; const ESTUARY_MIRROR_ID = 1; @@ -179,7 +179,6 @@ export const getAllCidsRequiredForPublish = async ( nodeId: number | undefined, versionId: number | undefined, ): Promise => { - // debugger; // ensure public data refs staged matches our data bucket cids const latestManifestEntry: ResearchObjectV1 = (await axios.get(`${PUBLIC_IPFS_PATH}/${manifestCid}`)).data; // const manifestString = manifestBuffer.toString('utf8'); @@ -443,12 +442,17 @@ export const cacheNodeMetadata = async (uuid: string, manifestCid: string, versi }, }); return { version, uuid, manifestCid }; - } catch (e) { - logger.error({ error: e }, 'Error cacheNodeMetadata'); + } catch (error) { + logger.error({ error }, 'Error cacheNodeMetadata'); return false; } }; +type DocumentInfo = { + documentId: DocumentId; + document: ResearchObjectDocument; +}; + export const showNodeDraftManifest = async (node: Node, ipfsFallbackUrl?: string) => { logger.trace('[getNodeManifest] ==> start'); const timeDifferenceInSeconds = getTimeDiffInSec(node.createdAt.toString()); @@ -457,10 +461,7 @@ export const showNodeDraftManifest = async (node: Node, ipfsFallbackUrl?: string '[getNodeManifest] ==> timeDifferenceInSeconds', ); - const cachedDraftMetadata = (await getFromCache(`node-draft-${node.uuid}`)) as { - documentId: DocumentId; - document: ResearchObjectDocument; - }; + const cachedDraftMetadata = await getFromCache(`node-draft-${ensureUuidEndsWithDot(node.uuid)}`, 0); logger.trace( { timeDifferenceInSeconds, uuid: node.uuid, cachedDraftMetadata: !!cachedDraftMetadata }, diff --git a/desci-server/src/services/orcid.ts b/desci-server/src/services/orcid.ts index 751be20d6..322b61eef 100644 --- a/desci-server/src/services/orcid.ts +++ b/desci-server/src/services/orcid.ts @@ -1,8 +1,10 @@ import { ResearchObjectV1, ResearchObjectV1Author } from '@desci-labs/desci-models'; import { ActionType, AuthTokenSource, ORCIDRecord, OrcidPutCodes, PutcodeReference } from '@prisma/client'; -import { logger as parentLogger, prisma, zeropad } from '../internal.js'; +import { prisma } from '../client.js'; +import { logger as parentLogger } from '../logger.js'; import { IndexedResearchObject, getIndexedResearchObjects } from '../theGraph.js'; +import { zeropad } from '../utils/manifest.js'; import { hexToCid } from '../utils.js'; import { attestationService } from './Attestation.js'; @@ -14,6 +16,7 @@ import { saveInteractionWithoutReq } from './interactionLog.js'; const DPID_URL_OVERRIDE = process.env.DPID_URL_OVERRIDE || 'https://beta.dpid.org'; const ORCID_DOMAIN = process.env.ORCID_API_DOMAIN || 'sandbox.orcid.org'; type Claim = Awaited>[number]; + const logger = parentLogger.child({ module: 'ORCIDApiService' }); /** diff --git a/desci-server/src/services/user.ts b/desci-server/src/services/user.ts index 28a039370..f333f44c9 100644 --- a/desci-server/src/services/user.ts +++ b/desci-server/src/services/user.ts @@ -153,7 +153,6 @@ export async function connectOrcidToUserIfPossible( logger.info({ fn: 'orcidCheck', user }, `Requesting user ${user}`); if (!user.orcid || user.orcid === orcid) { let nodeConnect: Awaited>; - debugger; if (!user.orcid || !(await isAuthTokenSetForUser(user.id))) { nodeConnect = await setOrcidForUser(user.id, orcid, { accessToken, diff --git a/desci-server/src/templates/emails/DoiMinted.tsx b/desci-server/src/templates/emails/DoiMinted.tsx index f6e2908ab..5ea18bc15 100644 --- a/desci-server/src/templates/emails/DoiMinted.tsx +++ b/desci-server/src/templates/emails/DoiMinted.tsx @@ -22,9 +22,10 @@ export interface DoiMintedEmailProps { userName: string; nodeTitle: string; doi: string; + doiLink: string; } -const DoiMintedEmail = ({ dpidPath, userName, nodeTitle, doi }: DoiMintedEmailProps) => ( +const DoiMintedEmail = ({ dpidPath, userName, nodeTitle, dpid, doi, doiLink }: DoiMintedEmailProps) => ( @@ -32,9 +33,12 @@ const DoiMintedEmail = ({ dpidPath, userName, nodeTitle, doi }: DoiMintedEmailPr - Hello ${userName}, You DOI registration for the research object ${nodeTitle} has been completed. Here is - your DOI: {doi} + Hello {userName}, DPID://{dpid} just received a DOI + + Your DOI registration for the research object {nodeTitle} has been completed. Here is your DOI: {doi} + + {doiLink}