mirror of
https://github.com/standardnotes/server
synced 2026-04-25 09:01:21 -04:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f8f65c3f | |||
| 3953dbc6b4 | |||
| 0b205287d1 | |||
| 4f0bc57b1a | |||
| 7d43316597 | |||
| 65d31f011b | |||
| 80dd6efae3 | |||
| a96f2c9153 | |||
| 225e0aaf88 | |||
| f0c85910bc | |||
| 124c443528 | |||
| 37c7f8d39f | |||
| c419f1ce22 | |||
| 4949cdfe2f | |||
| cd101b96ea | |||
| 40d0e4631f | |||
| a55a995660 | |||
| 1d576d48ad | |||
| 4ff8030f87 | |||
| c15e2e2c8f | |||
| 41d31a8d75 | |||
| 10e2a26352 | |||
| 6e547f77d0 | |||
| 530a426601 | |||
| 642d6bab77 | |||
| 7980af3d82 | |||
| 2980c42e88 | |||
| b03994f9db | |||
| 41906ec2f9 | |||
| 4d1e7ff2a5 | |||
| 7f18fcfc13 | |||
| ff02ce0747 | |||
| a6056600eb | |||
| 24c94326d5 | |||
| 48c0cb5e62 | |||
| 9968efe1b2 | |||
| 6368342149 | |||
| b5f73db210 | |||
| 22d6a02d04 | |||
| 4e0bcfcccf | |||
| 104313c15d | |||
| 814289af46 | |||
| 3096cd98d5 | |||
| 45dfefbc7a | |||
| 20d92149a8 | |||
| 9c01fffca5 | |||
| 61c1cfff4b | |||
| 7e74261f62 | |||
| 32601f34f1 | |||
| aef69a1a96 | |||
| 130f90bdb6 | |||
| 851c7de87f | |||
| 118156c62d | |||
| cdad3143c9 | |||
| 00fe32136e | |||
| 52f56eeb68 | |||
| b595264e31 | |||
| bf04262170 | |||
| fd589922bb | |||
| fb7029f5c1 | |||
| cc4b4f9bf8 | |||
| b048d6d7e3 | |||
| cffc1f442f | |||
| 91fe710741 | |||
| 5a1eb9fdac | |||
| a64ef6e750 | |||
| 47d2012b3d | |||
| 2c99cd2e21 | |||
| 435cd2f66a | |||
| 372b12dfc2 | |||
| 3a12f5c1c4 | |||
| 781de224b6 | |||
| eff09454c3 | |||
| 473feba6a8 | |||
| e9f0704fb0 | |||
| 8c99469d88 | |||
| 8ec1311dfc | |||
| e48cca6b45 | |||
| d660721f95 | |||
| c52bb93d79 | |||
| ffb6bfd0c9 | |||
| 6e0855f9b3 | |||
| ec9e9ec387 | |||
| fa75aa40f0 | |||
| b865953c22 | |||
| 2542cf6f9a | |||
| cb9499b87f | |||
| c351f01f67 | |||
| c87561fca7 | |||
| a363c143fa | |||
| fb81d2b926 | |||
| 05b1b8f079 | |||
| 7848dc06d4 | |||
| 3a005719b7 | |||
| 6928988f78 | |||
| a521894d7c | |||
| b7fb1d9c08 | |||
| 5f67e45911 | |||
| fddf9fccbd | |||
| 2bedbd7bd2 | |||
| 02f3c85796 | |||
| 3b5bd6a47f | |||
| 06fd404d44 | |||
| d931c52508 | |||
| 800fe9e4c8 | |||
| 8b3d78678f | |||
| 2351cd3ad6 | |||
| dd86c5bcdf | |||
| d0c00e306e | |||
| 6cd68ddd6a | |||
| 02639cddb2 | |||
| 0f67aa4058 | |||
| 78c3403d5f | |||
| fc8f8c574d | |||
| 3972ee580d | |||
| b0a994d5be | |||
| 80df28a0c4 | |||
| 1c6c6a9296 | |||
| 7bb698e442 | |||
| 784728cd54 | |||
| 4b883b68de | |||
| dec2cc2aaf | |||
| b4e8971ad2 | |||
| 84e436265e | |||
| ac8a69f8d4 | |||
| b912e050ea | |||
| 284561d093 | |||
| efc355982c | |||
| 8907879a19 | |||
| 86f6057207 | |||
| 4c92698c73 | |||
| 8407c3b649 | |||
| ed8f82617d | |||
| 31d040d1b6 | |||
| 25a6796e63 | |||
| ff091918aa | |||
| 91b76edce1 | |||
| 5ae5c83bf5 | |||
| 9d90f276de | |||
| 245f091e22 | |||
| ae2f8f086b | |||
| 5e5eb7f937 | |||
| 748630e1f1 | |||
| 43064c8c55 | |||
| 4559a3047c | |||
| de8064ee5c | |||
| 48c8dba342 | |||
| 31a515b2f1 | |||
| 294f56e189 | |||
| 70596a0aac | |||
| 74bc79116b | |||
| e6bd50ae77 | |||
| 308662550f | |||
| d94a7e7157 |
@@ -130,77 +130,77 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: yarn test ${{ inputs.package_path }}
|
run: yarn test ${{ inputs.package_path }}
|
||||||
|
|
||||||
# e2e:
|
e2e:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# needs: build
|
needs: build
|
||||||
|
|
||||||
# steps:
|
steps:
|
||||||
# - uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
# - name: Cache build
|
- name: Cache build
|
||||||
# id: cache-build
|
id: cache-build
|
||||||
# uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
# with:
|
with:
|
||||||
# path: |
|
path: |
|
||||||
# packages/**/dist
|
packages/**/dist
|
||||||
# ${{ needs.build.outputs.temp_dir }}
|
${{ needs.build.outputs.temp_dir }}
|
||||||
# key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
||||||
|
|
||||||
# - name: Set up Node
|
- name: Set up Node
|
||||||
# uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
# with:
|
with:
|
||||||
# registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
# node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
# - name: Build
|
- name: Build
|
||||||
# if: steps.cache-build.outputs.cache-hit != 'true'
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
# run: yarn build ${{ inputs.package_path }}
|
run: yarn build ${{ inputs.package_path }}
|
||||||
|
|
||||||
# - name: Bundle
|
- name: Bundle
|
||||||
# if: steps.cache-build.outputs.cache-hit != 'true'
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
# run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ needs.build.outputs.temp_dir }}
|
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ needs.build.outputs.temp_dir }}
|
||||||
|
|
||||||
# - name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
# uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
# with:
|
with:
|
||||||
# username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
# password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
# - name: Set up QEMU
|
- name: Set up QEMU
|
||||||
# uses: docker/setup-qemu-action@master
|
uses: docker/setup-qemu-action@master
|
||||||
# with:
|
with:
|
||||||
# platforms: all
|
platforms: all
|
||||||
|
|
||||||
# - name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
# id: buildx
|
id: buildx
|
||||||
# uses: docker/setup-buildx-action@master
|
uses: docker/setup-buildx-action@master
|
||||||
|
|
||||||
# - name: Publish Docker image for E2E testing
|
- name: Publish Docker image for E2E testing
|
||||||
# uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
# with:
|
with:
|
||||||
# builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
# context: ${{ needs.build.outputs.temp_dir }}
|
context: ${{ needs.build.outputs.temp_dir }}
|
||||||
# file: ${{ needs.build.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
file: ${{ needs.build.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
||||||
# platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
# push: true
|
push: true
|
||||||
# tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||||
|
|
||||||
# - name: Run E2E test suite
|
- name: Run E2E test suite
|
||||||
# uses: convictional/trigger-workflow-and-wait@v1.6.3
|
uses: convictional/trigger-workflow-and-wait@master
|
||||||
# with:
|
with:
|
||||||
# owner: standardnotes
|
owner: standardnotes
|
||||||
# repo: e2e
|
repo: e2e
|
||||||
# github_token: ${{ secrets.CI_PAT_TOKEN }}
|
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||||
# workflow_file_name: testing-with-stable-client.yml
|
workflow_file_name: testing-with-stable-client.yml
|
||||||
# wait_interval: 30
|
wait_interval: 30
|
||||||
# client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||||
# propagate_failure: true
|
propagate_failure: true
|
||||||
# trigger_workflow: true
|
trigger_workflow: true
|
||||||
# wait_workflow: true
|
wait_workflow: true
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: [ build, test, lint ]
|
needs: [ build, test, lint, e2e ]
|
||||||
|
|
||||||
name: Publish Docker Image
|
name: Publish Docker Image
|
||||||
uses: standardnotes/server/.github/workflows/common-docker-image.yml@main
|
uses: standardnotes/server/.github/workflows/common-docker-image.yml@main
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
"reference": "workspace:packages/security"\
|
"reference": "workspace:packages/security"\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
"name": "@standardnotes/settings",\
|
"name": "@standardnotes/settings-server",\
|
||||||
"reference": "workspace:packages/settings"\
|
"reference": "workspace:packages/settings"\
|
||||||
},\
|
},\
|
||||||
{\
|
{\
|
||||||
@@ -107,7 +107,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
||||||
["@standardnotes/security", ["workspace:packages/security"]],\
|
["@standardnotes/security", ["workspace:packages/security"]],\
|
||||||
["@standardnotes/server-monorepo", ["workspace:."]],\
|
["@standardnotes/server-monorepo", ["workspace:."]],\
|
||||||
["@standardnotes/settings", ["workspace:packages/settings"]],\
|
["@standardnotes/settings-server", ["workspace:packages/settings"]],\
|
||||||
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
||||||
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
||||||
["@standardnotes/time", ["workspace:packages/time"]],\
|
["@standardnotes/time", ["workspace:packages/time"]],\
|
||||||
@@ -126,7 +126,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||||
["@lerna-lite/list", "npm:1.6.0"],\
|
["@lerna-lite/list", "npm:1.6.0"],\
|
||||||
["@lerna-lite/run", "npm:1.6.0"],\
|
["@lerna-lite/run", "npm:1.6.0"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
@@ -2538,7 +2537,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/analytics/",\
|
"packageLocation": "./packages/analytics/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
@@ -2551,7 +2549,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@types/node", "npm:18.11.9"],\
|
["@types/node", "npm:18.11.9"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["dayjs", "npm:1.11.6"],\
|
["dayjs", "npm:1.11.6"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint", "npm:8.25.0"],\
|
["eslint", "npm:8.25.0"],\
|
||||||
@@ -2584,6 +2582,20 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.20.13", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip/node_modules/@standardnotes/api/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/api", "npm:1.20.13"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"],\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/api-gateway", [\
|
["@standardnotes/api-gateway", [\
|
||||||
@@ -2591,7 +2603,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/api-gateway/",\
|
"packageLocation": "./packages/api-gateway/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
|
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
@@ -2607,7 +2618,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@types/prettyjson", "npm:0.0.30"],\
|
["@types/prettyjson", "npm:0.0.30"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["axios", "npm:1.1.3"],\
|
["axios", "npm:1.1.3"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
@@ -2648,18 +2659,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/auth/",\
|
"packageLocation": "./packages/auth/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/auth-server", "workspace:packages/auth"],\
|
["@standardnotes/auth-server", "workspace:packages/auth"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/api", "npm:1.19.0"],\
|
["@standardnotes/api", "npm:1.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/features", "npm:1.53.1"],\
|
["@standardnotes/features", "npm:1.53.1"],\
|
||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||||
["@standardnotes/responses", "npm:1.11.1"],\
|
["@standardnotes/responses", "npm:1.11.1"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
|
||||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||||
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
@@ -2674,7 +2684,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||||
["@types/uuid", "npm:8.3.4"],\
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["axios", "npm:1.1.3"],\
|
["axios", "npm:1.1.3"],\
|
||||||
["bcryptjs", "npm:2.4.3"],\
|
["bcryptjs", "npm:2.4.3"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
@@ -2783,13 +2793,12 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/domain-events-infra/",\
|
"packageLocation": "./packages/domain-events-infra/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@types/ioredis", "npm:5.0.0"],\
|
["@types/ioredis", "npm:5.0.0"],\
|
||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
||||||
["ioredis", "npm:5.2.4"],\
|
["ioredis", "npm:5.2.4"],\
|
||||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||||
@@ -2816,6 +2825,19 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.19.21", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip/node_modules/@standardnotes/encryption/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"],\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/event-store", [\
|
["@standardnotes/event-store", [\
|
||||||
@@ -2823,7 +2845,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/event-store/",\
|
"packageLocation": "./packages/event-store/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/event-store", "workspace:packages/event-store"],\
|
["@standardnotes/event-store", "workspace:packages/event-store"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
@@ -2832,7 +2853,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@types/nodemailer", "npm:6.4.6"],\
|
["@types/nodemailer", "npm:6.4.6"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint", "npm:8.25.0"],\
|
["eslint", "npm:8.25.0"],\
|
||||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||||
@@ -2872,6 +2893,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.55.3", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip/node_modules/@standardnotes/features/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/features", "npm:1.55.3"],\
|
||||||
|
["@standardnotes/auth", "npm:3.19.4"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/files-server", [\
|
["@standardnotes/files-server", [\
|
||||||
@@ -2879,7 +2911,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/files/",\
|
"packageLocation": "./packages/files/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/files-server", "workspace:packages/files"],\
|
["@standardnotes/files-server", "workspace:packages/files"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/config", "npm:2.4.3"],\
|
["@standardnotes/config", "npm:2.4.3"],\
|
||||||
@@ -2899,7 +2930,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/prettyjson", "npm:0.0.30"],\
|
["@types/prettyjson", "npm:0.0.30"],\
|
||||||
["@types/uuid", "npm:8.3.4"],\
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["connect-busboy", "npm:1.0.0"],\
|
["connect-busboy", "npm:1.0.0"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dayjs", "npm:1.11.6"],\
|
["dayjs", "npm:1.11.6"],\
|
||||||
@@ -2954,6 +2985,13 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.38.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip/node_modules/@standardnotes/models/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/models", "npm:1.38.0"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/payloads", [\
|
["@standardnotes/payloads", [\
|
||||||
@@ -3007,6 +3045,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.12.9", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip/node_modules/@standardnotes/responses/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/responses", "npm:1.12.9"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/features", "npm:1.55.3"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/revisions-server", [\
|
["@standardnotes/revisions-server", [\
|
||||||
@@ -3014,7 +3063,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/revisions/",\
|
"packageLocation": "./packages/revisions/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
|
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/api", "npm:1.19.0"],\
|
["@standardnotes/api", "npm:1.19.0"],\
|
||||||
@@ -3032,7 +3080,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint", "npm:8.25.0"],\
|
["eslint", "npm:8.25.0"],\
|
||||||
@@ -3060,10 +3108,10 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/scheduler/",\
|
"packageLocation": "./packages/scheduler/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
|
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||||
@@ -3073,7 +3121,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@types/node", "npm:18.11.9"],\
|
["@types/node", "npm:18.11.9"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["dayjs", "npm:1.11.6"],\
|
["dayjs", "npm:1.11.6"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint", "npm:8.25.0"],\
|
["eslint", "npm:8.25.0"],\
|
||||||
@@ -3122,7 +3170,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||||
["@lerna-lite/list", "npm:1.6.0"],\
|
["@lerna-lite/list", "npm:1.6.0"],\
|
||||||
["@lerna-lite/run", "npm:1.6.0"],\
|
["@lerna-lite/run", "npm:1.6.0"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
@@ -3140,16 +3187,46 @@ const RAW_RUNTIME_STATE =
|
|||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/settings", [\
|
["@standardnotes/settings-server", [\
|
||||||
["workspace:packages/settings", {\
|
["workspace:packages/settings", {\
|
||||||
"packageLocation": "./packages/settings/",\
|
"packageLocation": "./packages/settings/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
["@standardnotes/settings-server", "workspace:packages/settings"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
["@standardnotes/api", "npm:1.20.13"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
|
["@types/cors", "npm:2.8.12"],\
|
||||||
|
["@types/dotenv", "npm:8.2.0"],\
|
||||||
|
["@types/express", "npm:4.17.14"],\
|
||||||
|
["@types/inversify-express-utils", "npm:2.0.0"],\
|
||||||
|
["@types/ioredis", "npm:5.0.0"],\
|
||||||
|
["@types/jest", "npm:29.1.1"],\
|
||||||
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
|
["cors", "npm:2.8.5"],\
|
||||||
|
["dotenv", "npm:16.0.1"],\
|
||||||
|
["eslint", "npm:8.25.0"],\
|
||||||
|
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||||
|
["express", "npm:4.18.2"],\
|
||||||
|
["helmet", "npm:6.0.0"],\
|
||||||
|
["inversify", "npm:6.0.1"],\
|
||||||
|
["inversify-express-utils", "npm:6.4.3"],\
|
||||||
|
["ioredis", "npm:5.2.4"],\
|
||||||
|
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||||
|
["mysql2", "npm:2.3.3"],\
|
||||||
|
["newrelic", "npm:9.6.0"],\
|
||||||
|
["npm-check-updates", "npm:16.0.1"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"]\
|
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
||||||
|
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
||||||
|
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
||||||
|
["winston", "npm:3.8.2"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
@@ -3162,6 +3239,14 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.13.3", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/sncrypto-node", [\
|
["@standardnotes/sncrypto-node", [\
|
||||||
@@ -3189,7 +3274,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/syncing-server/",\
|
"packageLocation": "./packages/syncing-server/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
@@ -3199,7 +3283,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@standardnotes/payloads", "npm:1.5.1"],\
|
["@standardnotes/payloads", "npm:1.5.1"],\
|
||||||
["@standardnotes/responses", "npm:1.11.1"],\
|
["@standardnotes/responses", "npm:1.11.1"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
|
||||||
["@standardnotes/time", "workspace:packages/time"],\
|
["@standardnotes/time", "workspace:packages/time"],\
|
||||||
["@types/cors", "npm:2.8.12"],\
|
["@types/cors", "npm:2.8.12"],\
|
||||||
["@types/dotenv", "npm:8.2.0"],\
|
["@types/dotenv", "npm:8.2.0"],\
|
||||||
@@ -3213,7 +3296,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||||
["@types/uuid", "npm:8.3.4"],\
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["axios", "npm:1.1.3"],\
|
["axios", "npm:1.1.3"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
@@ -3283,6 +3366,17 @@ const RAW_RUNTIME_STATE =
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.13.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip/node_modules/@standardnotes/utils/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/utils", "npm:1.13.0"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["dompurify", "npm:2.4.1"],\
|
||||||
|
["lodash", "npm:4.17.21"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/websockets-server", [\
|
["@standardnotes/websockets-server", [\
|
||||||
@@ -3290,7 +3384,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/websockets/",\
|
"packageLocation": "./packages/websockets/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/api", "npm:1.19.0"],\
|
["@standardnotes/api", "npm:1.19.0"],\
|
||||||
@@ -3304,7 +3397,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["axios", "npm:1.1.3"],\
|
["axios", "npm:1.1.3"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
@@ -3331,11 +3424,11 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/workspace/",\
|
"packageLocation": "./packages/workspace/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
||||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
|
||||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/api", "npm:1.19.0"],\
|
["@standardnotes/api", "npm:1.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/models", "npm:1.28.0"],\
|
["@standardnotes/models", "npm:1.28.0"],\
|
||||||
@@ -3347,7 +3440,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.4"],\
|
["@types/newrelic", "npm:7.0.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["cors", "npm:2.8.5"],\
|
["cors", "npm:2.8.5"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint", "npm:8.25.0"],\
|
["eslint", "npm:8.25.0"],\
|
||||||
@@ -4814,10 +4907,10 @@ const RAW_RUNTIME_STATE =
|
|||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["aws-sdk", [\
|
["aws-sdk", [\
|
||||||
["npm:2.1253.0", {\
|
["npm:2.1260.0", {\
|
||||||
"packageLocation": "./.yarn/cache/aws-sdk-npm-2.1253.0-2cf60975ab-faa4af2949.zip/node_modules/aws-sdk/",\
|
"packageLocation": "./.yarn/cache/aws-sdk-npm-2.1260.0-0145998ab1-9a1b2e4cb5.zip/node_modules/aws-sdk/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["buffer", "npm:4.9.2"],\
|
["buffer", "npm:4.9.2"],\
|
||||||
["events", "npm:1.1.1"],\
|
["events", "npm:1.1.1"],\
|
||||||
["ieee754", "npm:1.1.13"],\
|
["ieee754", "npm:1.1.13"],\
|
||||||
@@ -6382,6 +6475,13 @@ const RAW_RUNTIME_STATE =
|
|||||||
["dompurify", "npm:2.4.0"]\
|
["dompurify", "npm:2.4.0"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:2.4.1", {\
|
||||||
|
"packageLocation": "./.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip/node_modules/dompurify/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["dompurify", "npm:2.4.1"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["dot-prop", [\
|
["dot-prop", [\
|
||||||
@@ -12584,7 +12684,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["sqs-consumer", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:5.7.0"],\
|
["sqs-consumer", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:5.7.0"],\
|
||||||
["@types/aws-sdk", null],\
|
["@types/aws-sdk", null],\
|
||||||
["aws-sdk", "npm:2.1253.0"],\
|
["aws-sdk", "npm:2.1260.0"],\
|
||||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
|
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
|
||||||
],\
|
],\
|
||||||
"packagePeers": [\
|
"packagePeers": [\
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -61,7 +61,6 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "yarn@4.0.0-rc.25",
|
"packageManager": "yarn@4.0.0-rc.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/native-metrics": "^9.0.0",
|
|
||||||
"@sentry/node": "^7.19.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"newrelic": "^9.6.0"
|
"newrelic": "^9.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,164 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.12.25](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.24...@standardnotes/analytics@2.12.25) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.23...@standardnotes/analytics@2.12.24) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** daily analytics report template ([41906ec](https://github.com/standardnotes/server/commit/41906ec2f9fd4d605b1c002826173e14fb534e00))
|
||||||
|
|
||||||
|
## [2.12.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.22...@standardnotes/analytics@2.12.23) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.21...@standardnotes/analytics@2.12.22) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** report event publishing ([a605660](https://github.com/standardnotes/server/commit/a6056600eb96bf175189ad6d62870c9d736f331b))
|
||||||
|
|
||||||
|
## [2.12.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.20...@standardnotes/analytics@2.12.21) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add debug logs for report ([48c0cb5](https://github.com/standardnotes/server/commit/48c0cb5e62dc8af930de191deaa1eb3ff6c5a29f))
|
||||||
|
|
||||||
|
## [2.12.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.19...@standardnotes/analytics@2.12.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.18...@standardnotes/analytics@2.12.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.17...@standardnotes/analytics@2.12.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.16...@standardnotes/analytics@2.12.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.15...@standardnotes/analytics@2.12.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.14...@standardnotes/analytics@2.12.15) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.13...@standardnotes/analytics@2.12.14) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.12...@standardnotes/analytics@2.12.13) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.11...@standardnotes/analytics@2.12.12) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.10...@standardnotes/analytics@2.12.11) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.9...@standardnotes/analytics@2.12.10) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.8...@standardnotes/analytics@2.12.9) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.7...@standardnotes/analytics@2.12.8) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.6...@standardnotes/analytics@2.12.7) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.5...@standardnotes/analytics@2.12.6) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.4...@standardnotes/analytics@2.12.5) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.3...@standardnotes/analytics@2.12.4) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.2...@standardnotes/analytics@2.12.3) (2022-12-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.1...@standardnotes/analytics@2.12.2) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.0...@standardnotes/analytics@2.12.1) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
# [2.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.17...@standardnotes/analytics@2.12.0) (2022-12-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-core:** distinguish between username and email ([06fd404](https://github.com/standardnotes/server/commit/06fd404d44b44a53733f889aabd4da63f21e2f36))
|
||||||
|
|
||||||
|
## [2.11.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.16...@standardnotes/analytics@2.11.17) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.15...@standardnotes/analytics@2.11.16) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.13...@standardnotes/analytics@2.11.14) (2022-11-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.12...@standardnotes/analytics@2.11.13) (2022-11-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.11...@standardnotes/analytics@2.11.12) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.10...@standardnotes/analytics@2.11.11) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.9...@standardnotes/analytics@2.11.10) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.8...@standardnotes/analytics@2.11.9) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.7...@standardnotes/analytics@2.11.8) (2022-11-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.6...@standardnotes/analytics@2.11.7) (2022-11-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* binding of sns and sqs with additional config ([74bc791](https://github.com/standardnotes/server/commit/74bc79116bc50d9a5af1a558db1b7108dcda6d0e))
|
||||||
|
|
||||||
## [2.11.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.5...@standardnotes/analytics@2.11.6) (2022-11-22)
|
## [2.11.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.5...@standardnotes/analytics@2.11.6) (2022-11-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/analytics
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
|||||||
|
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||||
import { Period } from '../src/Domain/Time/Period'
|
import { Period } from '../src/Domain/Time/Period'
|
||||||
@@ -16,6 +17,8 @@ import TYPES from '../src/Bootstrap/Types'
|
|||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
const requestReport = async (
|
const requestReport = async (
|
||||||
analyticsStore: AnalyticsStoreInterface,
|
analyticsStore: AnalyticsStoreInterface,
|
||||||
@@ -24,6 +27,8 @@ const requestReport = async (
|
|||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||||
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||||
|
timer: TimerInterface,
|
||||||
|
adminEmails: string[],
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await calculateMonthlyRecurringRevenue.execute({})
|
await calculateMonthlyRecurringRevenue.execute({})
|
||||||
|
|
||||||
@@ -213,18 +218,29 @@ const requestReport = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
for (const adminEmail of adminEmails) {
|
||||||
activityStatistics: yesterdayActivityStatistics,
|
await domainEventPublisher.publish(
|
||||||
activityStatisticsOverTime: analyticsOverTime,
|
domainEventFactory.createEmailRequestedEvent({
|
||||||
statisticsOverTime,
|
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||||
statisticMeasures,
|
subject: getSubject(),
|
||||||
churn: {
|
body: getBody(
|
||||||
periodKeys: monthlyPeriodKeys,
|
{
|
||||||
values: churnRates,
|
activityStatistics: yesterdayActivityStatistics,
|
||||||
},
|
activityStatisticsOverTime: analyticsOverTime,
|
||||||
})
|
statisticsOverTime,
|
||||||
|
statisticMeasures,
|
||||||
await domainEventPublisher.publish(event)
|
churn: {
|
||||||
|
periodKeys: monthlyPeriodKeys,
|
||||||
|
values: churnRates,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timer,
|
||||||
|
),
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
userEmail: adminEmail,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
const container = new ContainerConfigLoader()
|
||||||
@@ -241,9 +257,13 @@ void container.load().then((container) => {
|
|||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||||
|
const timer: TimerInterface = container.get(TYPES.Timer)
|
||||||
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
||||||
TYPES.CalculateMonthlyRecurringRevenue,
|
TYPES.CalculateMonthlyRecurringRevenue,
|
||||||
)
|
)
|
||||||
|
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||||
|
|
||||||
|
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||||
|
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
requestReport(
|
requestReport(
|
||||||
@@ -253,6 +273,8 @@ void container.load().then((container) => {
|
|||||||
domainEventPublisher,
|
domainEventPublisher,
|
||||||
periodKeyGenerator,
|
periodKeyGenerator,
|
||||||
calculateMonthlyRecurringRevenue,
|
calculateMonthlyRecurringRevenue,
|
||||||
|
timer,
|
||||||
|
adminEmails,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/analytics worker
|
yarn workspace @standardnotes/analytics worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'report' )
|
'report' )
|
||||||
echo "Starting Usage Report Generation..."
|
echo "[Docker] Starting Usage Report Generation..."
|
||||||
yarn workspace @standardnotes/analytics report
|
yarn workspace @standardnotes/analytics report
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
coveragePathIgnorePatterns: ['/Infra/'],
|
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.11.6",
|
"version": "2.12.25",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
@@ -37,15 +37,14 @@
|
|||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/native-metrics": "^9.0.0",
|
|
||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.19.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
"@standardnotes/domain-core": "workspace:*",
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
"aws-sdk": "^2.1253.0",
|
"aws-sdk": "^2.1260.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"inversify": "^6.0.1",
|
"inversify": "^6.0.1",
|
||||||
|
|||||||
@@ -89,13 +89,24 @@ export class ContainerConfigLoader {
|
|||||||
})
|
})
|
||||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||||
|
|
||||||
if (env.get('SNS_AWS_REGION', true)) {
|
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||||
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
|
const snsConfig: AWS.SNS.Types.ClientConfiguration = {
|
||||||
new AWS.SNS({
|
apiVersion: 'latest',
|
||||||
apiVersion: 'latest',
|
region: env.get('SNS_AWS_REGION', true),
|
||||||
region: env.get('SNS_AWS_REGION', true),
|
}
|
||||||
}),
|
if (env.get('SNS_ENDPOINT', true)) {
|
||||||
)
|
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
|
||||||
|
}
|
||||||
|
if (env.get('SNS_DISABLE_SSL', true) === 'true') {
|
||||||
|
snsConfig.sslEnabled = false
|
||||||
|
}
|
||||||
|
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
|
||||||
|
snsConfig.credentials = {
|
||||||
|
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
|
||||||
|
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(new AWS.SNS(snsConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
@@ -119,6 +130,7 @@ export class ContainerConfigLoader {
|
|||||||
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
||||||
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||||
|
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const TYPES = {
|
|||||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||||
|
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||||
// Repositories
|
// Repositories
|
||||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { html } from './daily-analytics-report.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return `Daily analytics report ${new Date().toLocaleDateString('en-US')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(data: unknown, timer: TimerInterface): string {
|
||||||
|
return html(data, timer)
|
||||||
|
}
|
||||||
@@ -0,0 +1,966 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
|
const getChartUrls = (
|
||||||
|
data: any,
|
||||||
|
): {
|
||||||
|
subscriptions: string
|
||||||
|
users: string
|
||||||
|
quarterlyPerformance: string
|
||||||
|
churn: string
|
||||||
|
mrr: string
|
||||||
|
mrrMonthly: string
|
||||||
|
} => {
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionsLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: subscriptionPurchasingOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: subscriptionPurchasingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: subscriptionRenewingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Refunds',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: subscriptionRefundingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Cancels',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: subscriptionCancelledOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Reactivations',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: subscriptionReactivatedOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const usersLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: userRegistrationOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: userRegistrationOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Account Deletions',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: userDeletionOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarters = [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]
|
||||||
|
const quarterlyUserRegistrations = []
|
||||||
|
const quarterlySubscriptionPurchases = []
|
||||||
|
const quarterlySubscriptionRenewals = []
|
||||||
|
for (const quarter of quarters) {
|
||||||
|
const registrations =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const purchases =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const renewals =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
quarterlyUserRegistrations.push(registrations)
|
||||||
|
quarterlySubscriptionPurchases.push(purchases)
|
||||||
|
quarterlySubscriptionRenewals.push(renewals)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarterlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlyUserRegistrations,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionPurchases,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionRenewals,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Quarterly Performance',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyChurnRates = data.churn.values.map((value: { rate: number }) => +value.rate.toFixed(2))
|
||||||
|
|
||||||
|
const churnConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Churn Percent',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: monthlyChurnRates,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly Churn Rate',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Monthly Plans',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Annual Plans',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Five Year Plans',
|
||||||
|
backgroundColor: 'rgb(255, 120, 120)',
|
||||||
|
borderColor: 'rgb(255, 120, 120)',
|
||||||
|
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PRO Plans',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PLUS Plans',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||||
|
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||||
|
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||||
|
|
||||||
|
const mrrMonthlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgba(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: mrrMonthlyOverTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly MRR',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptions: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(subscriptionsLinerOverTimeConfig),
|
||||||
|
)}`,
|
||||||
|
users: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(usersLinerOverTimeConfig))}`,
|
||||||
|
quarterlyPerformance: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(quarterlyConfig),
|
||||||
|
)}`,
|
||||||
|
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||||
|
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||||
|
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const html = (data: any, timer: TimerInterface) => {
|
||||||
|
const chartUrls = getChartUrls(data)
|
||||||
|
|
||||||
|
const successfullPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentSuccess && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const failedPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentFailed && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const limitedDiscountPurchasedActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.LimitedDiscountOfferPurchased && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const incomeMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const refundMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
|
||||||
|
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
|
||||||
|
const revenueYesterday = incomeYesterday - refundsYesterday
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageYesterday = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const incomeMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const refundMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const revenueThisMonth = incomeThisMonth - refundsThisMonth
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const today = new Date()
|
||||||
|
const thisMonthPeriodKey = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString()}`
|
||||||
|
const thisMonthChurn = data.churn.values.find(
|
||||||
|
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ` <div>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from yesterday:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueYesterday.toLocaleString('en-US')}</b> (Income: $
|
||||||
|
${incomeYesterday.toLocaleString('en-US')}, Refunds: $${refundsYesterday.toLocaleString('en-US')})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Successfull payments: <b>${successfullPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Failed payments: <b>${failedPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>MRR Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Total:</b> $${mrrOverTime?.counts[mrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Subscription Type:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>PLUS:</b> $
|
||||||
|
${plusPlansMrrOverTime?.counts[plusPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>PRO:</b> $
|
||||||
|
${proPlansMrrOverTime?.counts[proPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Billing Frequency:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Monthly:</b> $
|
||||||
|
${monthlyPlansMrrOverTime?.counts[monthlyPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Annual:</b> $
|
||||||
|
${annualPlansMrrOverTime?.counts[annualPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>5-year:</b> $
|
||||||
|
${fiveYearPlansMrrOverTime?.counts[fiveYearPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered:${' '}
|
||||||
|
<b>
|
||||||
|
${userRegistrationOverTime?.counts[userRegistrationOverTime?.counts.length - 1]?.totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered:${' '}
|
||||||
|
<b>
|
||||||
|
${userDeletionOverTime?.counts[userDeletionOverTime?.counts.length - 1]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average account duration: ${registrationLengthDurationYesterday.days} days${' '}
|
||||||
|
${registrationLengthDurationYesterday.hours} hours ${registrationLengthDurationYesterday.minutes} minutes)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionPurchasingOverTime?.counts[
|
||||||
|
subscriptionPurchasingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(includes <b>${limitedDiscountPurchasedActivity?.totalCount.toLocaleString('en-US')}</b> limited time
|
||||||
|
offer purchases)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRenewingOverTime?.counts[
|
||||||
|
subscriptionRenewingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRefundingOverTime?.counts[
|
||||||
|
subscriptionRefundingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionCancelledOverTime?.counts[
|
||||||
|
subscriptionCancelledOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average subscription duration: ${subscriptionLengthDurationYesterday.days} days${' '}
|
||||||
|
${subscriptionLengthDurationYesterday.hours} hours ${subscriptionLengthDurationYesterday.minutes} minutes,
|
||||||
|
average remaining subscription percentage: ${subscriptionRemainingTimePercentageYesterday}%)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionReactivatedOverTime?.counts[
|
||||||
|
subscriptionReactivatedOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationYesterday.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from last 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Income: <b>$${incomeThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Refunds: <b>$${refundsThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered: <b>${userRegistrationOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered: <b>${userDeletionOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average account duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationLengthDurationThisMonth.days} days ${registrationLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>${subscriptionPurchasingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>${subscriptionRenewingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>${subscriptionRefundingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>${subscriptionCancelledOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>${subscriptionReactivatedOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionLengthDurationThisMonth.days} days ${subscriptionLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${subscriptionLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription remaining percentage this month:${' '}
|
||||||
|
<b>${subscriptionRemainingTimePercentageThisMonth}%</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationThisMonth.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrr}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrrMonthly}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the subscription chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.subscriptions}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the users chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.users}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the monthly churn rate percentage:</strong>
|
||||||
|
</p>
|
||||||
|
<p>✅ GREAT! Up to 7% 🔶 OKAY: 8-10% 🩸 BAD: 11 -15 % 🚨 TERRIBLE! 16-20%</p>
|
||||||
|
<p>Churn is calculated by the following formula:</p>
|
||||||
|
<p>
|
||||||
|
( Existing Customers Churn [${thisMonthChurn?.existingCustomersChurn}] + New Customers Churn [
|
||||||
|
${thisMonthChurn?.newCustomersChurn}] ) * 100 / Average Customers Count This Month [
|
||||||
|
${thisMonthChurn?.averageCustomersCount}]
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.churn}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is quarterly performance chart:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.quarterlyPerformance}></img>
|
||||||
|
<p>Thanks,SN</p>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
@@ -18,5 +18,5 @@ export class AnalyticsEntity {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
@Index('email')
|
@Index('email')
|
||||||
declare userEmail: string
|
declare username: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -9,55 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||||
|
createEmailRequestedEvent(dto: {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
userEmail: string
|
||||||
activityStatistics: Array<{
|
messageIdentifier: string
|
||||||
name: string
|
level: string
|
||||||
retention: number
|
body: string
|
||||||
totalCount: number
|
subject: string
|
||||||
}>
|
}): EmailRequestedEvent {
|
||||||
statisticMeasures: Array<{
|
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
averageCustomersCount: number
|
|
||||||
existingCustomersChurn: number
|
|
||||||
newCustomersChurn: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent {
|
|
||||||
return {
|
return {
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
type: 'EMAIL_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
meta: {
|
meta: {
|
||||||
correlation: {
|
correlation: {
|
||||||
userIdentifier: '',
|
userIdentifier: dto.userEmail,
|
||||||
userIdentifierType: 'uuid',
|
userIdentifierType: 'email',
|
||||||
},
|
},
|
||||||
origin: DomainEventService.Analytics,
|
origin: DomainEventService.Analytics,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,45 +1,11 @@
|
|||||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
activityStatistics: Array<{
|
userEmail: string
|
||||||
name: string
|
messageIdentifier: string
|
||||||
retention: number
|
level: string
|
||||||
totalCount: number
|
body: string
|
||||||
}>
|
subject: string
|
||||||
statisticMeasures: Array<{
|
}): EmailRequestedEvent
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
averageCustomersCount: number
|
|
||||||
existingCustomersChurn: number
|
|
||||||
newCustomersChurn: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { Email } from '@standardnotes/domain-core'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
@@ -41,7 +41,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { Email } from '@standardnotes/domain-core'
|
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
@@ -45,7 +45,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { Email } from '@standardnotes/domain-core'
|
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
@@ -69,7 +69,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { Email } from '@standardnotes/domain-core'
|
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
@@ -41,7 +41,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Email } from '@standardnotes/domain-core'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
@@ -41,7 +41,7 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
|||||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||||
let analyticsEntity = new AnalyticsEntity()
|
let analyticsEntity = new AnalyticsEntity()
|
||||||
analyticsEntity.userUuid = event.payload.userUuid
|
analyticsEntity.userUuid = event.payload.userUuid
|
||||||
analyticsEntity.userEmail = event.payload.email
|
analyticsEntity.username = event.payload.email
|
||||||
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
||||||
|
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { injectable } from 'inversify'
|
import { injectable } from 'inversify'
|
||||||
import { Email, MapperInterface, UniqueEntityId } from '@standardnotes/domain-core'
|
import { MapperInterface, UniqueEntityId, Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
||||||
@@ -14,7 +14,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
|
|||||||
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
||||||
const userOrError = User.create(
|
const userOrError = User.create(
|
||||||
{
|
{
|
||||||
email: Email.create(persistence.userEmail).getValue(),
|
username: Username.create(persistence.username).getValue(),
|
||||||
},
|
},
|
||||||
new UniqueEntityId(persistence.userUuid),
|
new UniqueEntityId(persistence.userUuid),
|
||||||
)
|
)
|
||||||
@@ -70,7 +70,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
|
|||||||
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
||||||
persistence.subscriptionId = subscription.id.toValue() as number
|
persistence.subscriptionId = subscription.id.toValue() as number
|
||||||
persistence.subscriptionPlan = subscription.props.planName.value
|
persistence.subscriptionPlan = subscription.props.planName.value
|
||||||
persistence.userEmail = user.props.email.value
|
persistence.username = user.props.username.value
|
||||||
persistence.userUuid = user.id.toString()
|
persistence.userUuid = user.id.toString()
|
||||||
persistence.createdAt = domain.props.createdAt
|
persistence.createdAt = domain.props.createdAt
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Email } from '@standardnotes/domain-core'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { Subscription } from '../Subscription/Subscription'
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
@@ -19,7 +19,7 @@ describe('RevenueModification', () => {
|
|||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
}).getValue()
|
}).getValue()
|
||||||
user = User.create({
|
user = User.create({
|
||||||
email: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
}).getValue()
|
}).getValue()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('GetUserAnalyticsId', () => {
|
|||||||
analyticsEntity = {
|
analyticsEntity = {
|
||||||
id: 123,
|
id: 123,
|
||||||
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
||||||
userEmail: 'test@test.te',
|
username: 'test@test.te',
|
||||||
} as jest.Mocked<AnalyticsEntity>
|
} as jest.Mocked<AnalyticsEntity>
|
||||||
|
|
||||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Email, Uuid } from '@standardnotes/domain-core'
|
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
||||||
@@ -28,7 +28,7 @@ export class GetUserAnalyticsId implements UseCaseInterface {
|
|||||||
return {
|
return {
|
||||||
analyticsId: analyticsEntity.id,
|
analyticsId: analyticsEntity.id,
|
||||||
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
||||||
userEmail: Email.create(analyticsEntity.userEmail).getValue(),
|
username: Username.create(analyticsEntity.username).getValue(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { Email, Uuid } from '@standardnotes/domain-core'
|
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export type GetUserAnalyticsIdResponse = {
|
export type GetUserAnalyticsIdResponse = {
|
||||||
analyticsId: number
|
analyticsId: number
|
||||||
userEmail: Email
|
username: Username
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,7 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { Email, Result, Uuid } from '@standardnotes/domain-core'
|
import { Result, Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 2,
|
payedAmount: 2,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ export class SaveRevenueModification implements DomainUseCaseInterface<RevenueMo
|
|||||||
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
||||||
const userOrError = User.create(
|
const userOrError = User.create(
|
||||||
{
|
{
|
||||||
email: dto.userEmail,
|
username: dto.username,
|
||||||
},
|
},
|
||||||
new UniqueEntityId(dto.userUuid.value),
|
new UniqueEntityId(dto.userUuid.value),
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import { Email, Uuid } from '@standardnotes/domain-core'
|
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||||
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
||||||
@@ -9,7 +9,7 @@ export interface SaveRevenueModificationDTO {
|
|||||||
planName: SubscriptionPlanName
|
planName: SubscriptionPlanName
|
||||||
newSubscriber: boolean
|
newSubscriber: boolean
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
userEmail: Email
|
username: Username
|
||||||
subscriptionId: number
|
subscriptionId: number
|
||||||
billingFrequency: number
|
billingFrequency: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Email } from '@standardnotes/domain-core'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
|
|
||||||
describe('User', () => {
|
describe('User', () => {
|
||||||
it('should create an entity', () => {
|
it('should create an entity', () => {
|
||||||
const user = User.create({
|
const user = User.create({
|
||||||
email: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
}).getValue()
|
}).getValue()
|
||||||
|
|
||||||
expect(user.id.toString()).toHaveLength(36)
|
expect(user.id.toString()).toHaveLength(36)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Email } from '@standardnotes/domain-core'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface UserProps {
|
export interface UserProps {
|
||||||
email: Email
|
username: Username
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class TypeORMRevenueModification {
|
|||||||
length: 255,
|
length: 255,
|
||||||
})
|
})
|
||||||
@Index('email')
|
@Index('email')
|
||||||
declare userEmail: string
|
declare username: string
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'user_uuid',
|
name: 'user_uuid',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ WEB_SOCKET_SERVER_URL=http://websockets:3000
|
|||||||
PAYMENTS_SERVER_URL=http://payments:3000
|
PAYMENTS_SERVER_URL=http://payments:3000
|
||||||
FILES_SERVER_URL=http://files:3000
|
FILES_SERVER_URL=http://files:3000
|
||||||
REVISIONS_SERVER_URL=http://revisions:3000
|
REVISIONS_SERVER_URL=http://revisions:3000
|
||||||
|
EMAIL_SERVER_URL=http://email:3000
|
||||||
|
|
||||||
HTTP_CALL_TIMEOUT=60000
|
HTTP_CALL_TIMEOUT=60000
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,118 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.1...@standardnotes/api-gateway@1.40.2) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.40.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.0...@standardnotes/api-gateway@1.40.1) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.24...@standardnotes/api-gateway@1.40.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add unsubscribe from emails endpoint ([22d6a02](https://github.com/standardnotes/api-gateway/commit/22d6a02d049ba3bde890c7def91e19f013ba3e22))
|
||||||
|
|
||||||
|
## [1.39.24](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.23...@standardnotes/api-gateway@1.39.24) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.23](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.22...@standardnotes/api-gateway@1.39.23) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.22](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.21...@standardnotes/api-gateway@1.39.22) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.21](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.20...@standardnotes/api-gateway@1.39.21) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.20](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.19...@standardnotes/api-gateway@1.39.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.19](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.18...@standardnotes/api-gateway@1.39.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.18](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.17...@standardnotes/api-gateway@1.39.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.17](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.16...@standardnotes/api-gateway@1.39.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.15...@standardnotes/api-gateway@1.39.16) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.14...@standardnotes/api-gateway@1.39.15) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.13...@standardnotes/api-gateway@1.39.14) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.13](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.12...@standardnotes/api-gateway@1.39.13) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.11...@standardnotes/api-gateway@1.39.12) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.10...@standardnotes/api-gateway@1.39.11) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.9...@standardnotes/api-gateway@1.39.10) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.8...@standardnotes/api-gateway@1.39.9) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.7...@standardnotes/api-gateway@1.39.8) (2022-12-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.6...@standardnotes/api-gateway@1.39.7) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.5...@standardnotes/api-gateway@1.39.6) (2022-11-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.4...@standardnotes/api-gateway@1.39.5) (2022-11-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.3...@standardnotes/api-gateway@1.39.4) (2022-11-25)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** make revisions and workspace server urls optional ([8907879](https://github.com/standardnotes/api-gateway/commit/8907879a194d2d8328fbd3ca8ec9d0b608c2da50))
|
||||||
|
|
||||||
|
## [1.39.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.2...@standardnotes/api-gateway@1.39.3) (2022-11-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.1...@standardnotes/api-gateway@1.39.2) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.0...@standardnotes/api-gateway@1.39.1) (2022-11-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
# [1.39.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.38.9...@standardnotes/api-gateway@1.39.0) (2022-11-22)
|
# [1.39.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.38.9...@standardnotes/api-gateway@1.39.0) (2022-11-22)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.39.0",
|
"version": "1.40.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/native-metrics": "^9.0.0",
|
|
||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.19.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/common": "workspace:^",
|
"@standardnotes/common": "workspace:^",
|
||||||
@@ -28,7 +27,7 @@
|
|||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/security": "workspace:*",
|
"@standardnotes/security": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
"aws-sdk": "^2.1253.0",
|
"aws-sdk": "^2.1260.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
|
|||||||
@@ -54,11 +54,12 @@ export class ContainerConfigLoader {
|
|||||||
// env vars
|
// env vars
|
||||||
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
|
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
|
||||||
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
||||||
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL'))
|
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
|
||||||
|
container.bind(TYPES.EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
|
||||||
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
||||||
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
||||||
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||||
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL'))
|
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL', true))
|
||||||
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
|
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
|
||||||
container
|
container
|
||||||
.bind(TYPES.HTTP_CALL_TIMEOUT)
|
.bind(TYPES.HTTP_CALL_TIMEOUT)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const TYPES = {
|
|||||||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||||
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
||||||
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
|
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
|
||||||
|
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
|
||||||
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
||||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||||
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||||
|
|||||||
@@ -29,4 +29,14 @@ export class ActionsController extends BaseHttpController {
|
|||||||
async methods(request: Request, response: Response): Promise<void> {
|
async methods(request: Request, response: Response): Promise<void> {
|
||||||
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
|
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@httpGet('/unsubscribe/:token')
|
||||||
|
async emailUnsubscribe(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.httpService.callEmailServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
`subscriptions/actions/unsubscribe/${request.params.token}`,
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export class HttpService implements HttpServiceInterface {
|
|||||||
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
||||||
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
||||||
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
|
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
|
||||||
|
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
|
||||||
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||||
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||||
@inject(TYPES.Logger) private logger: Logger,
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
@@ -39,6 +40,11 @@ export class HttpService implements HttpServiceInterface {
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
payload?: Record<string, unknown> | string,
|
payload?: Record<string, unknown> | string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (!this.revisionsServerUrl) {
|
||||||
|
response.status(400).send({ message: 'Revisions Server not configured' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
|
await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,12 +66,33 @@ export class HttpService implements HttpServiceInterface {
|
|||||||
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async callEmailServer(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
endpoint: string,
|
||||||
|
payload?: Record<string, unknown> | string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.emailServerUrl) {
|
||||||
|
response.status(400).send({ message: 'Email Server not configured' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
|
||||||
|
}
|
||||||
|
|
||||||
async callWorkspaceServer(
|
async callWorkspaceServer(
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
payload?: Record<string, unknown> | string,
|
payload?: Record<string, unknown> | string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (!this.workspaceServerUrl) {
|
||||||
|
response.status(400).send({ message: 'Workspace Server not configured' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
export interface HttpServiceInterface {
|
export interface HttpServiceInterface {
|
||||||
|
callEmailServer(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
endpoint: string,
|
||||||
|
payload?: Record<string, unknown> | string,
|
||||||
|
): Promise<void>
|
||||||
callAuthServer(
|
callAuthServer(
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
|
|||||||
@@ -3,6 +3,182 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.67.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.0...@standardnotes/auth-server@1.67.1) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* user signed in email template ([c15e2e2](https://github.com/standardnotes/server/commit/c15e2e2c8f3a6c177e227d25440501fa38dd3d0e))
|
||||||
|
|
||||||
|
# [1.67.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.9...@standardnotes/auth-server@1.67.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add email subscription unsubscribed event handler ([10e2a26](https://github.com/standardnotes/server/commit/10e2a263522dfa33c06940f29cb77f783f66b20c))
|
||||||
|
|
||||||
|
## [1.66.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.8...@standardnotes/auth-server@1.66.9) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.7...@standardnotes/auth-server@1.66.8) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.6...@standardnotes/auth-server@1.66.7) (2022-12-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** linter issue ([104313c](https://github.com/standardnotes/server/commit/104313c15df79f6308d23e21f65111e5bd3d9c72))
|
||||||
|
|
||||||
|
## [1.66.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.5...@standardnotes/auth-server@1.66.6) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.4...@standardnotes/auth-server@1.66.5) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.3...@standardnotes/auth-server@1.66.4) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.2...@standardnotes/auth-server@1.66.3) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.1...@standardnotes/auth-server@1.66.2) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.0...@standardnotes/auth-server@1.66.1) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.66.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.65.0...@standardnotes/auth-server@1.66.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **email:** replace offline subscription token created event in favour of email requested ([b595264](https://github.com/standardnotes/server/commit/b595264e313ac5ae5404f6a4a05b90b8c11f7f02))
|
||||||
|
|
||||||
|
# [1.65.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.7...@standardnotes/auth-server@1.65.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove offline subscription token created event in favour of email requested ([fd58992](https://github.com/standardnotes/server/commit/fd589922bba29595a0dfd154a42fe158024fad28))
|
||||||
|
|
||||||
|
## [1.64.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.6...@standardnotes/auth-server@1.64.7) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.5...@standardnotes/auth-server@1.64.6) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.4...@standardnotes/auth-server@1.64.5) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.3...@standardnotes/auth-server@1.64.4) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.2...@standardnotes/auth-server@1.64.3) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.1...@standardnotes/auth-server@1.64.2) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.0...@standardnotes/auth-server@1.64.1) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.64.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.63.2...@standardnotes/auth-server@1.64.0) (2022-12-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** replace user signed in events with email requested ([e48cca6](https://github.com/standardnotes/server/commit/e48cca6b45b02876f2d82b726c1d2f124d90b587))
|
||||||
|
|
||||||
|
## [1.63.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.63.1...@standardnotes/auth-server@1.63.2) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.63.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.63.0...@standardnotes/auth-server@1.63.1) (2022-12-07)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** remove not needed event from factory ([2542cf6](https://github.com/standardnotes/server/commit/2542cf6f9a40c3a5eb4e11ead3cbbc25afefae48))
|
||||||
|
|
||||||
|
# [1.63.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.62.1...@standardnotes/auth-server@1.63.0) (2022-12-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-core:** rename email subscription rejection level to email level ([c87561f](https://github.com/standardnotes/server/commit/c87561fca782883b84f58b4f0b9f85ecc279ca50))
|
||||||
|
|
||||||
|
## [1.62.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.62.0...@standardnotes/auth-server@1.62.1) (2022-12-06)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** remove redundant specs and fix stream query ([fb81d2b](https://github.com/standardnotes/server/commit/fb81d2b9260cf7bee3e3e6911d5a6e8eb1d650e3))
|
||||||
|
|
||||||
|
# [1.62.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.61.0...@standardnotes/auth-server@1.62.0) (2022-12-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add procedure for email subscriptions sync ([7848dc0](https://github.com/standardnotes/server/commit/7848dc06d4f4fe8c380ed45c32e23ac0e62014fa))
|
||||||
|
|
||||||
|
# [1.61.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.17...@standardnotes/auth-server@1.61.0) (2022-12-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add publishing mute emails setting changed event ([6928988](https://github.com/standardnotes/server/commit/6928988f7855c939f2365e35cb6cb0ff18e5c37a))
|
||||||
|
|
||||||
|
## [1.60.17](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.16...@standardnotes/auth-server@1.60.17) (2022-12-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.16](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.15...@standardnotes/auth-server@1.60.16) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.15](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.14...@standardnotes/auth-server@1.60.15) (2022-11-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.13...@standardnotes/auth-server@1.60.14) (2022-11-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.12...@standardnotes/auth-server@1.60.13) (2022-11-25)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** bring back streaming all users in an email campaign send out ([8407c3b](https://github.com/standardnotes/server/commit/8407c3b64910c87591a97b856f5b0c0aebc98e51))
|
||||||
|
|
||||||
|
## [1.60.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.11...@standardnotes/auth-server@1.60.12) (2022-11-25)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** tmp test email campaign black friday 2022 reminder on team only ([25a6796](https://github.com/standardnotes/server/commit/25a6796e636bc30de99001bd16a2a1084b608b6a))
|
||||||
|
|
||||||
|
## [1.60.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.10...@standardnotes/auth-server@1.60.11) (2022-11-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.9...@standardnotes/auth-server@1.60.10) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.8...@standardnotes/auth-server@1.60.9) (2022-11-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.60.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.7...@standardnotes/auth-server@1.60.8) (2022-11-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* binding of sns and sqs with additional config ([74bc791](https://github.com/standardnotes/server/commit/74bc79116bc50d9a5af1a558db1b7108dcda6d0e))
|
||||||
|
|
||||||
## [1.60.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.6...@standardnotes/auth-server@1.60.7) (2022-11-22)
|
## [1.60.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.6...@standardnotes/auth-server@1.60.7) (2022-11-22)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+21
-22
@@ -3,20 +3,19 @@ import 'reflect-metadata'
|
|||||||
import 'newrelic'
|
import 'newrelic'
|
||||||
|
|
||||||
import { Stream } from 'stream'
|
import { Stream } from 'stream'
|
||||||
|
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import * as dayjs from 'dayjs'
|
import * as dayjs from 'dayjs'
|
||||||
import * as utc from 'dayjs/plugin/utc'
|
import * as utc from 'dayjs/plugin/utc'
|
||||||
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
import { PermissionName } from '@standardnotes/features'
|
||||||
|
|
||||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||||
import TYPES from '../src/Bootstrap/Types'
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||||
import { MuteFailedBackupsEmailsOption, MuteFailedCloudBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
|
||||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
|
||||||
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
||||||
|
|
||||||
const inputArgs = process.argv.slice(2)
|
const inputArgs = process.argv.slice(2)
|
||||||
@@ -30,38 +29,38 @@ const requestBackups = async (
|
|||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
let settingName: SettingName,
|
let settingName: string,
|
||||||
permissionName: PermissionName,
|
permissionName: PermissionName,
|
||||||
muteEmailsSettingName: SettingName,
|
muteEmailsSettingName: string,
|
||||||
muteEmailsSettingValue: string,
|
muteEmailsSettingValue: string,
|
||||||
providerTokenSettingName: SettingName
|
providerTokenSettingName: string
|
||||||
switch (backupProvider) {
|
switch (backupProvider) {
|
||||||
case 'email':
|
case 'email':
|
||||||
settingName = SettingName.EmailBackupFrequency
|
settingName = SettingName.NAMES.EmailBackupFrequency
|
||||||
permissionName = PermissionName.DailyEmailBackup
|
permissionName = PermissionName.DailyEmailBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
break
|
break
|
||||||
case 'dropbox':
|
case 'dropbox':
|
||||||
settingName = SettingName.DropboxBackupFrequency
|
settingName = SettingName.NAMES.DropboxBackupFrequency
|
||||||
permissionName = PermissionName.DailyDropboxBackup
|
permissionName = PermissionName.DailyDropboxBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.DropboxBackupToken
|
providerTokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||||
break
|
break
|
||||||
case 'one_drive':
|
case 'one_drive':
|
||||||
settingName = SettingName.OneDriveBackupFrequency
|
settingName = SettingName.NAMES.OneDriveBackupFrequency
|
||||||
permissionName = PermissionName.DailyOneDriveBackup
|
permissionName = PermissionName.DailyOneDriveBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.OneDriveBackupToken
|
providerTokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||||
break
|
break
|
||||||
case 'google_drive':
|
case 'google_drive':
|
||||||
settingName = SettingName.GoogleDriveBackupFrequency
|
settingName = SettingName.NAMES.GoogleDriveBackupFrequency
|
||||||
permissionName = PermissionName.DailyGDriveBackup
|
permissionName = PermissionName.DailyGDriveBackup
|
||||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
muteEmailsSettingValue = 'muted'
|
||||||
providerTokenSettingName = SettingName.GoogleDriveBackupToken
|
providerTokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
|
|
||||||
import 'newrelic'
|
|
||||||
|
|
||||||
import { Stream } from 'stream'
|
|
||||||
|
|
||||||
import { Logger } from 'winston'
|
|
||||||
import * as dayjs from 'dayjs'
|
|
||||||
import * as utc from 'dayjs/plugin/utc'
|
|
||||||
|
|
||||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
|
||||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
|
||||||
import TYPES from '../src/Bootstrap/Types'
|
|
||||||
import { Env } from '../src/Bootstrap/Env'
|
|
||||||
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
|
||||||
import { UserSubscriptionRepositoryInterface } from '../src/Domain/Subscription/UserSubscriptionRepositoryInterface'
|
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
|
||||||
import { MuteMarketingEmailsOption, SettingName } from '@standardnotes/settings'
|
|
||||||
import { EmailMessageIdentifier } from '@standardnotes/common'
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
|
||||||
|
|
||||||
const inputArgs = process.argv.slice(2)
|
|
||||||
const emailMessageIdentifier = inputArgs[0]
|
|
||||||
|
|
||||||
const sendEmailCampaign = async (
|
|
||||||
userRepository: UserRepositoryInterface,
|
|
||||||
settingService: SettingServiceInterface,
|
|
||||||
userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
|
||||||
timer: TimerInterface,
|
|
||||||
domainEventFactory: DomainEventFactoryInterface,
|
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
|
||||||
logger: Logger,
|
|
||||||
): Promise<void> => {
|
|
||||||
const stream = await userRepository.streamAll()
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stream
|
|
||||||
.pipe(
|
|
||||||
new Stream.Transform({
|
|
||||||
objectMode: true,
|
|
||||||
transform: async (rawUserData, _encoding, callback) => {
|
|
||||||
try {
|
|
||||||
const emailsMutedSetting = await settingService.findSettingWithDecryptedValue({
|
|
||||||
userUuid: rawUserData.user_uuid,
|
|
||||||
settingName: SettingName.MuteMarketingEmails,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (emailsMutedSetting === null || emailsMutedSetting.value === MuteMarketingEmailsOption.Muted) {
|
|
||||||
callback()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeSubscription = false
|
|
||||||
let subscriptionPlanName = null
|
|
||||||
|
|
||||||
const userSubscription = await userSubscriptionRepository.findOneByUserUuid(rawUserData.user_uuid)
|
|
||||||
if (userSubscription !== null) {
|
|
||||||
activeSubscription =
|
|
||||||
!userSubscription.cancelled && userSubscription.endsAt > timer.getTimestampInMicroseconds()
|
|
||||||
subscriptionPlanName = userSubscription.planName
|
|
||||||
}
|
|
||||||
|
|
||||||
await domainEventPublisher.publish(
|
|
||||||
domainEventFactory.createEmailMessageRequestedEvent({
|
|
||||||
userEmail: rawUserData.user_email,
|
|
||||||
messageIdentifier: emailMessageIdentifier as EmailMessageIdentifier,
|
|
||||||
context: {
|
|
||||||
activeSubscription,
|
|
||||||
subscriptionPlanName,
|
|
||||||
muteEmailsSettingUuid: emailsMutedSetting.uuid,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Could not process user ${rawUserData.user_uuid}: ${(error as Error).message}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback()
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.on('finish', resolve)
|
|
||||||
.on('error', reject)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
|
||||||
void container.load().then((container) => {
|
|
||||||
dayjs.extend(utc)
|
|
||||||
|
|
||||||
const env: Env = new Env()
|
|
||||||
env.load()
|
|
||||||
|
|
||||||
const logger: Logger = container.get(TYPES.Logger)
|
|
||||||
|
|
||||||
logger.info(`Starting email campaign for email ${emailMessageIdentifier} ...`)
|
|
||||||
|
|
||||||
if (!emailMessageIdentifier) {
|
|
||||||
logger.error('No email message identifier passed as argument. Skipped sending.')
|
|
||||||
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const userRepository: UserRepositoryInterface = container.get(TYPES.UserRepository)
|
|
||||||
const settingService: SettingServiceInterface = container.get(TYPES.SettingService)
|
|
||||||
const userSubscriptionRepository: UserSubscriptionRepositoryInterface = container.get(
|
|
||||||
TYPES.UserSubscriptionRepository,
|
|
||||||
)
|
|
||||||
const timer: TimerInterface = container.get(TYPES.Timer)
|
|
||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
|
||||||
|
|
||||||
Promise.resolve(
|
|
||||||
sendEmailCampaign(
|
|
||||||
userRepository,
|
|
||||||
settingService,
|
|
||||||
userSubscriptionRepository,
|
|
||||||
timer,
|
|
||||||
domainEventFactory,
|
|
||||||
domainEventPublisher,
|
|
||||||
logger,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
logger.info(`${emailMessageIdentifier} email campaign complete.`)
|
|
||||||
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error(`Could not finish ${emailMessageIdentifier} email campaign: ${error.message}`)
|
|
||||||
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -12,7 +12,7 @@ import { Env } from '../src/Bootstrap/Env'
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||||
import { MuteFailedBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||||
@@ -28,8 +28,8 @@ const requestBackups = async (
|
|||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const permissionName = PermissionName.DailyEmailBackup
|
const permissionName = PermissionName.DailyEmailBackup
|
||||||
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||||
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
const muteEmailsSettingValue = 'muted'
|
||||||
|
|
||||||
if (!backupEmail) {
|
if (!backupEmail) {
|
||||||
throw new Error('Could not trigger email backup for user - missing email parameter')
|
throw new Error('Could not trigger email backup for user - missing email parameter')
|
||||||
|
|||||||
@@ -5,64 +5,58 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-local' )
|
'start-local' )
|
||||||
echo "Starting Web..."
|
echo "[Docker] Starting Web..."
|
||||||
yarn workspace @standardnotes/auth-server start:local
|
yarn workspace @standardnotes/auth-server start:local
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'start-web' )
|
'start-web' )
|
||||||
echo "Starting Web..."
|
echo "[Docker] Starting Web..."
|
||||||
yarn workspace @standardnotes/auth-server start
|
yarn workspace @standardnotes/auth-server start
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/auth-server worker
|
yarn workspace @standardnotes/auth-server worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'email-daily-backup' )
|
'email-daily-backup' )
|
||||||
echo "Starting Email Daily Backup..."
|
echo "[Docker] Starting Email Daily Backup..."
|
||||||
yarn workspace @standardnotes/auth-server daily-backup:email
|
yarn workspace @standardnotes/auth-server daily-backup:email
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'email-weekly-backup' )
|
'email-weekly-backup' )
|
||||||
echo "Starting Email Weekly Backup..."
|
echo "[Docker] Starting Email Weekly Backup..."
|
||||||
yarn workspace @standardnotes/auth-server weekly-backup:email
|
yarn workspace @standardnotes/auth-server weekly-backup:email
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'email-backup' )
|
'email-backup' )
|
||||||
echo "Starting Email Backup For Single User..."
|
echo "[Docker] Starting Email Backup For Single User..."
|
||||||
EMAIL=$1 && shift 1
|
EMAIL=$1 && shift 1
|
||||||
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
|
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'dropbox-daily-backup' )
|
'dropbox-daily-backup' )
|
||||||
echo "Starting Dropbox Daily Backup..."
|
echo "[Docker] Starting Dropbox Daily Backup..."
|
||||||
yarn workspace @standardnotes/auth-server daily-backup:dropbox
|
yarn workspace @standardnotes/auth-server daily-backup:dropbox
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'google-drive-daily-backup' )
|
'google-drive-daily-backup' )
|
||||||
echo "Starting Google Drive Daily Backup..."
|
echo "[Docker] Starting Google Drive Daily Backup..."
|
||||||
yarn workspace @standardnotes/auth-server daily-backup:google_drive
|
yarn workspace @standardnotes/auth-server daily-backup:google_drive
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'one-drive-daily-backup' )
|
'one-drive-daily-backup' )
|
||||||
echo "Starting One Drive Daily Backup..."
|
echo "[Docker] Starting One Drive Daily Backup..."
|
||||||
yarn workspace @standardnotes/auth-server daily-backup:one_drive
|
yarn workspace @standardnotes/auth-server daily-backup:one_drive
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'email-campaign' )
|
|
||||||
echo "Starting Email Campaign Sending..."
|
|
||||||
MESSAGE_IDENTIFIER=$1 && shift 1
|
|
||||||
yarn workspace @standardnotes/auth-server email-campaign $MESSAGE_IDENTIFIER
|
|
||||||
;;
|
|
||||||
|
|
||||||
'content-recalculation' )
|
'content-recalculation' )
|
||||||
echo "Starting Content Size Recalculation..."
|
echo "[Docker] Starting Content Size Recalculation..."
|
||||||
yarn workspace @standardnotes/auth-server content-recalculation
|
yarn workspace @standardnotes/auth-server content-recalculation
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', 'HealthCheckController'],
|
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', '/Infra/', '/Projection/', '/Domain/Email/'],
|
||||||
setupFilesAfterEnv: ['./test-setup.ts'],
|
setupFilesAfterEnv: ['./test-setup.ts'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Redis, { Cluster } from 'ioredis'
|
import Redis, { Cluster } from 'ioredis'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
import { Setting } from '../src/Domain/Setting/Setting'
|
import { Setting } from '../src/Domain/Setting/Setting'
|
||||||
@@ -34,7 +34,7 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa
|
|||||||
|
|
||||||
const setting = new Setting()
|
const setting = new Setting()
|
||||||
setting.uuid = item['uuid']
|
setting.uuid = item['uuid']
|
||||||
setting.name = SettingName.MfaSecret
|
setting.name = SettingName.NAMES.MfaSecret
|
||||||
setting.value = item['content']
|
setting.value = item['content']
|
||||||
if (item['deleted']) {
|
if (item['deleted']) {
|
||||||
setting.value = null
|
setting.value = null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.60.7",
|
"version": "1.67.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
@@ -26,27 +26,25 @@
|
|||||||
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
|
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
|
||||||
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
||||||
"content-recalculation": "yarn node dist/bin/content.js",
|
"content-recalculation": "yarn node dist/bin/content.js",
|
||||||
"email-campaign": "yarn node dist/bin/email.js",
|
|
||||||
"typeorm": "typeorm-ts-node-commonjs",
|
"typeorm": "typeorm-ts-node-commonjs",
|
||||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/native-metrics": "^9.0.0",
|
|
||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.19.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/api": "^1.19.0",
|
"@standardnotes/api": "^1.19.0",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/features": "^1.52.1",
|
"@standardnotes/features": "^1.52.1",
|
||||||
"@standardnotes/predicates": "workspace:*",
|
"@standardnotes/predicates": "workspace:*",
|
||||||
"@standardnotes/responses": "^1.6.39",
|
"@standardnotes/responses": "^1.6.39",
|
||||||
"@standardnotes/security": "workspace:*",
|
"@standardnotes/security": "workspace:*",
|
||||||
"@standardnotes/settings": "workspace:*",
|
|
||||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||||
"@standardnotes/sncrypto-node": "workspace:*",
|
"@standardnotes/sncrypto-node": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
"aws-sdk": "^2.1253.0",
|
"aws-sdk": "^2.1260.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ import { SubscriptionInvitesController } from '../Controller/SubscriptionInvites
|
|||||||
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||||
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
||||||
import { UserRequestsController } from '../Controller/UserRequestsController'
|
import { UserRequestsController } from '../Controller/UserRequestsController'
|
||||||
|
import { EmailSubscriptionUnsubscribedEventHandler } from '../Domain/Handler/EmailSubscriptionUnsubscribedEventHandler'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
@@ -235,6 +236,12 @@ export class ContainerConfigLoader {
|
|||||||
apiVersion: 'latest',
|
apiVersion: 'latest',
|
||||||
region: env.get('SNS_AWS_REGION', true),
|
region: env.get('SNS_AWS_REGION', true),
|
||||||
}
|
}
|
||||||
|
if (env.get('SNS_ENDPOINT', true)) {
|
||||||
|
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
|
||||||
|
}
|
||||||
|
if (env.get('SNS_DISABLE_SSL', true) === 'true') {
|
||||||
|
snsConfig.sslEnabled = false
|
||||||
|
}
|
||||||
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
|
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
|
||||||
snsConfig.credentials = {
|
snsConfig.credentials = {
|
||||||
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
|
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
|
||||||
@@ -554,6 +561,15 @@ export class ContainerConfigLoader {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind<EmailSubscriptionUnsubscribedEventHandler>(TYPES.EmailSubscriptionUnsubscribedEventHandler)
|
||||||
|
.toConstantValue(
|
||||||
|
new EmailSubscriptionUnsubscribedEventHandler(
|
||||||
|
container.get(TYPES.UserRepository),
|
||||||
|
container.get(TYPES.SettingService),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
||||||
@@ -576,6 +592,7 @@ export class ContainerConfigLoader {
|
|||||||
],
|
],
|
||||||
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
||||||
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
||||||
|
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.EmailSubscriptionUnsubscribedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ const TYPES = {
|
|||||||
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
||||||
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
||||||
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
||||||
|
EmailSubscriptionUnsubscribedEventHandler: Symbol.for('EmailSubscriptionUnsubscribedEventHandler'),
|
||||||
// Services
|
// Services
|
||||||
DeviceDetector: Symbol.for('DeviceDetector'),
|
DeviceDetector: Symbol.for('DeviceDetector'),
|
||||||
SessionService: Symbol.for('SessionService'),
|
SessionService: Symbol.for('SessionService'),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Request } from 'express'
|
import { Request } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -69,7 +69,7 @@ export class AdminController extends BaseHttpController {
|
|||||||
const result = await this.doDeleteSetting.execute({
|
const result = await this.doDeleteSetting.execute({
|
||||||
uuid,
|
uuid,
|
||||||
userUuid,
|
userUuid,
|
||||||
settingName: SettingName.MfaSecret,
|
settingName: SettingName.NAMES.MfaSecret,
|
||||||
timestamp: updatedAt,
|
timestamp: updatedAt,
|
||||||
softDelete: true,
|
softDelete: true,
|
||||||
})
|
})
|
||||||
@@ -115,7 +115,7 @@ export class AdminController extends BaseHttpController {
|
|||||||
|
|
||||||
const result = await this.doDeleteSetting.execute({
|
const result = await this.doDeleteSetting.execute({
|
||||||
userUuid,
|
userUuid,
|
||||||
settingName: SettingName.EmailBackupFrequency,
|
settingName: SettingName.NAMES.EmailBackupFrequency,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +20,7 @@ export class SubscriptionSettingsController extends BaseHttpController {
|
|||||||
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||||
const result = await this.doGetSubscriptionSetting.execute({
|
const result = await this.doGetSubscriptionSetting.execute({
|
||||||
userUuid: response.locals.user.uuid,
|
userUuid: response.locals.user.uuid,
|
||||||
subscriptionSettingName: request.params.subscriptionSettingName as SubscriptionSettingName,
|
subscriptionSettingName: request.params.subscriptionSettingName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
||||||
import { ErrorTag, RoleName } from '@standardnotes/common'
|
import { ErrorTag, RoleName } from '@standardnotes/common'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
@@ -77,7 +77,7 @@ export class SubscriptionTokensController extends BaseHttpController {
|
|||||||
const user = authenticateTokenResponse.user as User
|
const user = authenticateTokenResponse.user as User
|
||||||
let extensionKey = undefined
|
let extensionKey = undefined
|
||||||
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ExtensionKey,
|
settingName: SettingName.NAMES.ExtensionKey,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (extensionKeySetting !== null) {
|
if (extensionKeySetting !== null) {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './offline-subscription-token-created.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return 'Access to your Standard Notes Subscription Dashboard'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(email: string, offlineSubscriptionDashboardUrl: string): string {
|
||||||
|
return html(email, offlineSubscriptionDashboardUrl)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './shared-subscription-invitation-created.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return 'You have been invited to a Standard Notes subscription'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(inviterIdentifier: string, inviteUuid: string): string {
|
||||||
|
return html(inviterIdentifier, inviteUuid)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './user-signed-in.html'
|
||||||
|
|
||||||
|
export function getSubject(email: string): string {
|
||||||
|
return `New sign-in for ${email}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(email: string, device: string, browser: string, date: Date): string {
|
||||||
|
return html(email, device, browser, date.toLocaleString())
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => `<div class="sn-component">
|
||||||
|
<div class="sk-panel static">
|
||||||
|
<div class="sk-panel-content">
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<h1 class="h1 title sk-panel-row">
|
||||||
|
<div class="sk-panel-column">
|
||||||
|
Access your Standard Notes Subscription Dashboard,
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
<div class="faded sk-panel-row small">Registered as ${userEmail}</div>
|
||||||
|
</div>
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<div class="title">Link to your subscription dashboard: <a
|
||||||
|
href="${offlineSubscriptionDashboardUrl}">${offlineSubscriptionDashboardUrl}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<p>
|
||||||
|
Get help any time by visiting our <a href="https://standardnotes.com/help">Help page</a>
|
||||||
|
or by replying directly to this email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const html = (inviterIdentifier: string, inviteUuid: string) => `<p>Hello,</p>
|
||||||
|
<p>You've been invited to join a Standard Notes premium subscription at no cost. ${inviterIdentifier} has invited you to share the benefits of their subscription plan.</p>
|
||||||
|
<p>
|
||||||
|
<a href='https://app.standardnotes.com/?accept-subscription-invite=${inviteUuid}'>Accept Invite</a>
|
||||||
|
</p>`
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export const html = (email: string, device: string, browser: string, timeAndDate: string) => `
|
||||||
|
<div>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>We've detected a new sign-in to your account ${email}</p>
|
||||||
|
<p>
|
||||||
|
<b>Device type</b>: ${device}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Browser type</b>: ${browser}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Time and date</strong>: <span>${timeAndDate}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If this was you, please disregard this email. If it wasn't you, we recommend signing into your account and
|
||||||
|
changing your password immediately, then enabling 2FA.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Thanks,
|
||||||
|
<br />
|
||||||
|
SN
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
import { EmailMessageIdentifier, JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
import { JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
||||||
import {
|
import {
|
||||||
AccountDeletionRequestedEvent,
|
AccountDeletionRequestedEvent,
|
||||||
UserEmailChangedEvent,
|
UserEmailChangedEvent,
|
||||||
UserRegisteredEvent,
|
UserRegisteredEvent,
|
||||||
UserRolesChangedEvent,
|
UserRolesChangedEvent,
|
||||||
OfflineSubscriptionTokenCreatedEvent,
|
|
||||||
EmailBackupRequestedEvent,
|
EmailBackupRequestedEvent,
|
||||||
CloudBackupRequestedEvent,
|
CloudBackupRequestedEvent,
|
||||||
ListedAccountRequestedEvent,
|
ListedAccountRequestedEvent,
|
||||||
UserSignedInEvent,
|
|
||||||
UserDisabledSessionUserAgentLoggingEvent,
|
UserDisabledSessionUserAgentLoggingEvent,
|
||||||
SharedSubscriptionInvitationCreatedEvent,
|
SharedSubscriptionInvitationCreatedEvent,
|
||||||
SharedSubscriptionInvitationCanceledEvent,
|
SharedSubscriptionInvitationCanceledEvent,
|
||||||
PredicateVerifiedEvent,
|
PredicateVerifiedEvent,
|
||||||
DomainEventService,
|
DomainEventService,
|
||||||
EmailMessageRequestedEvent,
|
|
||||||
WebSocketMessageRequestedEvent,
|
WebSocketMessageRequestedEvent,
|
||||||
ExitDiscountApplyRequestedEvent,
|
ExitDiscountApplyRequestedEvent,
|
||||||
UserContentSizeRecalculationRequestedEvent,
|
UserContentSizeRecalculationRequestedEvent,
|
||||||
|
MuteEmailsSettingChangedEvent,
|
||||||
|
EmailRequestedEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
@@ -32,6 +31,25 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||||
|
|
||||||
|
createMuteEmailsSettingChangedEvent(dto: {
|
||||||
|
username: string
|
||||||
|
mute: boolean
|
||||||
|
emailSubscriptionRejectionLevel: string
|
||||||
|
}): MuteEmailsSettingChangedEvent {
|
||||||
|
return {
|
||||||
|
type: 'MUTE_EMAILS_SETTING_CHANGED',
|
||||||
|
createdAt: this.timer.getUTCDate(),
|
||||||
|
meta: {
|
||||||
|
correlation: {
|
||||||
|
userIdentifier: dto.username,
|
||||||
|
userIdentifierType: 'email',
|
||||||
|
},
|
||||||
|
origin: DomainEventService.Auth,
|
||||||
|
},
|
||||||
|
payload: dto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent {
|
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent {
|
||||||
return {
|
return {
|
||||||
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
|
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
|
||||||
@@ -82,13 +100,15 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEmailMessageRequestedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
userEmail: string
|
userEmail: string
|
||||||
messageIdentifier: EmailMessageIdentifier
|
messageIdentifier: string
|
||||||
context: Record<string, unknown>
|
level: string
|
||||||
}): EmailMessageRequestedEvent {
|
body: string
|
||||||
|
subject: string
|
||||||
|
}): EmailRequestedEvent {
|
||||||
return {
|
return {
|
||||||
type: 'EMAIL_MESSAGE_REQUESTED',
|
type: 'EMAIL_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
meta: {
|
meta: {
|
||||||
correlation: {
|
correlation: {
|
||||||
@@ -182,28 +202,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createUserSignedInEvent(dto: {
|
|
||||||
userUuid: string
|
|
||||||
userEmail: string
|
|
||||||
device: string
|
|
||||||
browser: string
|
|
||||||
signInAlertEnabled: boolean
|
|
||||||
muteSignInEmailsSettingUuid: Uuid
|
|
||||||
}): UserSignedInEvent {
|
|
||||||
return {
|
|
||||||
type: 'USER_SIGNED_IN',
|
|
||||||
createdAt: this.timer.getUTCDate(),
|
|
||||||
meta: {
|
|
||||||
correlation: {
|
|
||||||
userIdentifier: dto.userUuid,
|
|
||||||
userIdentifierType: 'uuid',
|
|
||||||
},
|
|
||||||
origin: DomainEventService.Auth,
|
|
||||||
},
|
|
||||||
payload: dto,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent {
|
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent {
|
||||||
return {
|
return {
|
||||||
type: 'LISTED_ACCOUNT_REQUESTED',
|
type: 'LISTED_ACCOUNT_REQUESTED',
|
||||||
@@ -291,24 +289,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent {
|
|
||||||
return {
|
|
||||||
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED',
|
|
||||||
createdAt: this.timer.getUTCDate(),
|
|
||||||
meta: {
|
|
||||||
correlation: {
|
|
||||||
userIdentifier: email,
|
|
||||||
userIdentifierType: 'email',
|
|
||||||
},
|
|
||||||
origin: DomainEventService.Auth,
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
token,
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createUserRegisteredEvent(dto: {
|
createUserRegisteredEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
email: string
|
email: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Uuid, RoleName, EmailMessageIdentifier, ProtocolVersion, JSONString } from '@standardnotes/common'
|
import { Uuid, RoleName, ProtocolVersion, JSONString } from '@standardnotes/common'
|
||||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||||
import {
|
import {
|
||||||
AccountDeletionRequestedEvent,
|
AccountDeletionRequestedEvent,
|
||||||
@@ -6,37 +6,30 @@ import {
|
|||||||
UserRegisteredEvent,
|
UserRegisteredEvent,
|
||||||
UserRolesChangedEvent,
|
UserRolesChangedEvent,
|
||||||
UserEmailChangedEvent,
|
UserEmailChangedEvent,
|
||||||
OfflineSubscriptionTokenCreatedEvent,
|
|
||||||
EmailBackupRequestedEvent,
|
EmailBackupRequestedEvent,
|
||||||
ListedAccountRequestedEvent,
|
ListedAccountRequestedEvent,
|
||||||
UserSignedInEvent,
|
|
||||||
UserDisabledSessionUserAgentLoggingEvent,
|
UserDisabledSessionUserAgentLoggingEvent,
|
||||||
SharedSubscriptionInvitationCreatedEvent,
|
SharedSubscriptionInvitationCreatedEvent,
|
||||||
SharedSubscriptionInvitationCanceledEvent,
|
SharedSubscriptionInvitationCanceledEvent,
|
||||||
PredicateVerifiedEvent,
|
PredicateVerifiedEvent,
|
||||||
EmailMessageRequestedEvent,
|
|
||||||
WebSocketMessageRequestedEvent,
|
WebSocketMessageRequestedEvent,
|
||||||
ExitDiscountApplyRequestedEvent,
|
ExitDiscountApplyRequestedEvent,
|
||||||
UserContentSizeRecalculationRequestedEvent,
|
UserContentSizeRecalculationRequestedEvent,
|
||||||
|
MuteEmailsSettingChangedEvent,
|
||||||
|
EmailRequestedEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent
|
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent
|
||||||
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
|
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
|
||||||
createEmailMessageRequestedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
userEmail: string
|
userEmail: string
|
||||||
messageIdentifier: EmailMessageIdentifier
|
messageIdentifier: string
|
||||||
context: Record<string, unknown>
|
level: string
|
||||||
}): EmailMessageRequestedEvent
|
body: string
|
||||||
createUserSignedInEvent(dto: {
|
subject: string
|
||||||
userUuid: string
|
}): EmailRequestedEvent
|
||||||
userEmail: string
|
|
||||||
device: string
|
|
||||||
browser: string
|
|
||||||
signInAlertEnabled: boolean
|
|
||||||
muteSignInEmailsSettingUuid: Uuid
|
|
||||||
}): UserSignedInEvent
|
|
||||||
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent
|
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent
|
||||||
createUserRegisteredEvent(dto: {
|
createUserRegisteredEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
@@ -62,7 +55,6 @@ export interface DomainEventFactoryInterface {
|
|||||||
}): AccountDeletionRequestedEvent
|
}): AccountDeletionRequestedEvent
|
||||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
|
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
|
||||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||||
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent
|
|
||||||
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
email: string
|
email: string
|
||||||
@@ -91,4 +83,9 @@ export interface DomainEventFactoryInterface {
|
|||||||
userEmail: string
|
userEmail: string
|
||||||
discountCode: string
|
discountCode: string
|
||||||
}): ExitDiscountApplyRequestedEvent
|
}): ExitDiscountApplyRequestedEvent
|
||||||
|
createMuteEmailsSettingChangedEvent(dto: {
|
||||||
|
username: string
|
||||||
|
mute: boolean
|
||||||
|
emailSubscriptionRejectionLevel: string
|
||||||
|
}): MuteEmailsSettingChangedEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
|
import { EmailSubscriptionUnsubscribedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
import { EmailSubscriptionUnsubscribedEventHandler } from './EmailSubscriptionUnsubscribedEventHandler'
|
||||||
|
|
||||||
|
describe('EmailSubscriptionUnsubscribedEventHandler', () => {
|
||||||
|
let userRepository: UserRepositoryInterface
|
||||||
|
let settingsService: SettingServiceInterface
|
||||||
|
let event: EmailSubscriptionUnsubscribedEvent
|
||||||
|
|
||||||
|
const createHandler = () => new EmailSubscriptionUnsubscribedEventHandler(userRepository, settingsService)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||||
|
userRepository.findOneByEmail = jest.fn().mockReturnValue({} as jest.Mocked<User>)
|
||||||
|
|
||||||
|
settingsService = {} as jest.Mocked<SettingServiceInterface>
|
||||||
|
settingsService.createOrReplace = jest.fn()
|
||||||
|
|
||||||
|
event = {
|
||||||
|
payload: {
|
||||||
|
userEmail: 'test@test.te',
|
||||||
|
level: EmailLevel.LEVELS.Marketing,
|
||||||
|
},
|
||||||
|
} as jest.Mocked<EmailSubscriptionUnsubscribedEvent>
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not do anything if user is not found', async () => {
|
||||||
|
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user marketing email settings', async () => {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_MARKETING_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user sign in email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.SignIn
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_SIGN_IN_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user email backup email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.FailedEmailBackup
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_FAILED_BACKUPS_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user email backup email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.FailedCloudBackup
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_FAILED_CLOUD_BACKUPS_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error for unrecognized level', async () => {
|
||||||
|
event.payload.level = 'foobar'
|
||||||
|
|
||||||
|
let caughtError = null
|
||||||
|
try {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
|
import { DomainEventHandlerInterface, EmailSubscriptionUnsubscribedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { SettingName } from '@standardnotes/settings'
|
||||||
|
|
||||||
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
|
||||||
|
export class EmailSubscriptionUnsubscribedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(private userRepository: UserRepositoryInterface, private settingsService: SettingServiceInterface) {}
|
||||||
|
|
||||||
|
async handle(event: EmailSubscriptionUnsubscribedEvent): Promise<void> {
|
||||||
|
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||||
|
if (user === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.settingsService.createOrReplace({
|
||||||
|
user,
|
||||||
|
props: {
|
||||||
|
name: this.getSettingNameFromLevel(event.payload.level),
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSettingNameFromLevel(level: string): string {
|
||||||
|
switch (level) {
|
||||||
|
case EmailLevel.LEVELS.FailedCloudBackup:
|
||||||
|
return SettingName.MuteFailedCloudBackupsEmails
|
||||||
|
case EmailLevel.LEVELS.FailedEmailBackup:
|
||||||
|
return SettingName.MuteFailedBackupsEmails
|
||||||
|
case EmailLevel.LEVELS.Marketing:
|
||||||
|
return SettingName.MuteMarketingEmails
|
||||||
|
case EmailLevel.LEVELS.SignIn:
|
||||||
|
return SettingName.MuteSignInEmails
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown level: ${level}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ExtensionKeyGrantedEvent } from '@standardnotes/domain-events'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
||||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
@@ -54,7 +54,7 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
|||||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
userSubscriptionUuid: subscription.uuid,
|
userSubscriptionUuid: subscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (bytesUsedSetting === null) {
|
if (bytesUsedSetting === null) {
|
||||||
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
|
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
|
||||||
@@ -51,7 +51,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
|||||||
await this.subscriptionSettingService.createOrReplace({
|
await this.subscriptionSettingService.createOrReplace({
|
||||||
userSubscription: subscription,
|
userSubscription: subscription,
|
||||||
props: {
|
props: {
|
||||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
|||||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: (await subscription.user).uuid,
|
userUuid: (await subscription.user).uuid,
|
||||||
userSubscriptionUuid: subscription.uuid,
|
userSubscriptionUuid: subscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (bytesUsedSetting !== null) {
|
if (bytesUsedSetting !== null) {
|
||||||
bytesUsed = bytesUsedSetting.value as string
|
bytesUsed = bytesUsedSetting.value as string
|
||||||
@@ -56,7 +56,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
|||||||
await this.subscriptionSettingService.createOrReplace({
|
await this.subscriptionSettingService.createOrReplace({
|
||||||
userSubscription: subscription,
|
userSubscription: subscription,
|
||||||
props: {
|
props: {
|
||||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ListedAccountCreatedEvent } from '@standardnotes/domain-events'
|
||||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
|||||||
|
|
||||||
const newSecret = { authorId: event.payload.userId, secret: event.payload.secret, hostUrl: event.payload.hostUrl }
|
const newSecret = { authorId: event.payload.userId, secret: event.payload.secret, hostUrl: event.payload.hostUrl }
|
||||||
|
|
||||||
let authSecrets: ListedAuthorSecretsData = [newSecret]
|
let authSecrets = [newSecret]
|
||||||
|
|
||||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ListedAuthorSecrets,
|
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (listedAuthorSecretsSetting !== null) {
|
if (listedAuthorSecretsSetting !== null) {
|
||||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||||
existingSecrets.push(newSecret)
|
existingSecrets.push(newSecret)
|
||||||
authSecrets = existingSecrets
|
authSecrets = existingSecrets
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ListedAuthorSecrets,
|
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
unencryptedValue: JSON.stringify(authSecrets),
|
unencryptedValue: JSON.stringify(authSecrets),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DomainEventHandlerInterface, ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, ListedAccountDeletedEvent } from '@standardnotes/domain-events'
|
||||||
import { ListedAuthorSecretsData, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.ListedAuthorSecrets,
|
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
if (listedAuthorSecretsSetting === null) {
|
if (listedAuthorSecretsSetting === null) {
|
||||||
@@ -33,9 +33,9 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||||
const filteredSecrets = existingSecrets.filter(
|
const filteredSecrets = existingSecrets.filter(
|
||||||
(secret) =>
|
(secret: Record<string, unknown>) =>
|
||||||
secret.authorId !== event.payload.userId ||
|
secret.authorId !== event.payload.userId ||
|
||||||
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
||||||
)
|
)
|
||||||
@@ -43,7 +43,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ListedAuthorSecrets,
|
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||||
unencryptedValue: JSON.stringify(filteredSecrets),
|
unencryptedValue: JSON.stringify(filteredSecrets),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|||||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
@@ -48,7 +48,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
|||||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||||
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
@@ -95,7 +95,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
|||||||
await this.settingService.createOrReplace({
|
await this.settingService.createOrReplace({
|
||||||
user,
|
user,
|
||||||
props: {
|
props: {
|
||||||
name: SettingName.ExtensionKey,
|
name: SettingName.NAMES.ExtensionKey,
|
||||||
unencryptedValue: event.payload.extensionKey,
|
unencryptedValue: event.payload.extensionKey,
|
||||||
serverEncryptionVersion: EncryptionVersion.Default,
|
serverEncryptionVersion: EncryptionVersion.Default,
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { EphemeralSession } from './EphemeralSession'
|
|||||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
|
||||||
import { Setting } from '../Setting/Setting'
|
import { Setting } from '../Setting/Setting'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
|
|
||||||
@@ -171,7 +170,7 @@ describe('SessionService', () => {
|
|||||||
user.uuid = '123'
|
user.uuid = '123'
|
||||||
|
|
||||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
|
|
||||||
const sessionPayload = await createService().createNewSessionForUser({
|
const sessionPayload = await createService().createNewSessionForUser({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { EphemeralSession } from './EphemeralSession'
|
|||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { SessionBody } from '@standardnotes/responses'
|
import { SessionBody } from '@standardnotes/responses'
|
||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
@@ -291,7 +291,7 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
|
|
||||||
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
||||||
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||||
settingName: SettingName.LogSessionUserAgent,
|
settingName: SettingName.NAMES.LogSessionUserAgent,
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -299,6 +299,6 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return loggingSetting.value === LogSessionUserAgentOption.Enabled
|
return loggingSetting.value === 'enabled'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
export type FindSettingDTO = {
|
export type FindSettingDTO = {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
settingName: SettingName
|
settingName: string
|
||||||
settingUuid?: Uuid
|
settingUuid?: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Uuid } from '@standardnotes/common'
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
export type FindSubscriptionSettingDTO = {
|
export type FindSubscriptionSettingDTO = {
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
userSubscriptionUuid: Uuid
|
userSubscriptionUuid: Uuid
|
||||||
subscriptionSettingName: SubscriptionSettingName
|
subscriptionSettingName: string
|
||||||
settingUuid?: Uuid
|
settingUuid?: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import {
|
|||||||
CloudBackupRequestedEvent,
|
CloudBackupRequestedEvent,
|
||||||
DomainEventPublisherInterface,
|
DomainEventPublisherInterface,
|
||||||
EmailBackupRequestedEvent,
|
EmailBackupRequestedEvent,
|
||||||
|
MuteEmailsSettingChangedEvent,
|
||||||
UserDisabledSessionUserAgentLoggingEvent,
|
UserDisabledSessionUserAgentLoggingEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import {
|
import { MuteMarketingEmailsOption } from '@standardnotes/settings'
|
||||||
EmailBackupFrequency,
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
LogSessionUserAgentOption,
|
|
||||||
OneDriveBackupFrequency,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
@@ -57,6 +54,9 @@ describe('SettingInterpreter', () => {
|
|||||||
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
|
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
|
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
|
||||||
|
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
logger.debug = jest.fn()
|
logger.debug = jest.fn()
|
||||||
@@ -66,11 +66,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.LogSessionUserAgent,
|
name: SettingName.NAMES.LogSessionUserAgent,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, LogSessionUserAgentOption.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
||||||
@@ -81,11 +81,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Daily,
|
value: 'daily',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false)
|
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false)
|
||||||
@@ -93,16 +93,16 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Daily,
|
value: 'daily',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||||
name: SettingName.MuteFailedBackupsEmails,
|
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
uuid: '6-7-8',
|
uuid: '6-7-8',
|
||||||
value: 'muted',
|
value: 'muted',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true)
|
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true)
|
||||||
@@ -110,12 +110,12 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should not trigger backup if email backup setting is disabled', async () => {
|
it('should not trigger backup if email backup setting is disabled', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.EmailBackupFrequency,
|
name: SettingName.NAMES.EmailBackupFrequency,
|
||||||
value: EmailBackupFrequency.Disabled,
|
value: 'disabled',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
||||||
@@ -123,7 +123,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.DropboxBackupToken,
|
name: SettingName.NAMES.DropboxBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -142,11 +142,11 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if dropbox backup setting is created - muted emails', async () => {
|
it('should trigger cloud backup if dropbox backup setting is created - muted emails', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.DropboxBackupToken,
|
name: SettingName.NAMES.DropboxBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||||
name: SettingName.MuteFailedCloudBackupsEmails,
|
name: SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
uuid: '6-7-8',
|
uuid: '6-7-8',
|
||||||
value: 'muted',
|
value: 'muted',
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
@@ -165,7 +165,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.GoogleDriveBackupToken,
|
name: SettingName.NAMES.GoogleDriveBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -184,7 +184,7 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should trigger cloud backup if one drive backup setting is created', async () => {
|
it('should trigger cloud backup if one drive backup setting is created', async () => {
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
value: 'test-token',
|
value: 'test-token',
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
@@ -201,15 +201,32 @@ describe('SettingInterpreter', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
|
||||||
|
const setting = {
|
||||||
|
name: SettingName.MuteMarketingEmails,
|
||||||
|
value: MuteMarketingEmailsOption.Muted,
|
||||||
|
} as jest.Mocked<Setting>
|
||||||
|
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
await createInterpreter().interpretSettingUpdated(setting, user, MuteMarketingEmailsOption.Muted)
|
||||||
|
|
||||||
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
|
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
|
||||||
|
emailSubscriptionRejectionLevel: 'MARKETING',
|
||||||
|
mute: true,
|
||||||
|
username: 'test@test.te',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
|
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'encrypted-backup-token',
|
value: 'encrypted-backup-token',
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: 'daily',
|
value: 'daily',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
@@ -229,19 +246,19 @@ describe('SettingInterpreter', () => {
|
|||||||
|
|
||||||
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||||
name: SettingName.OneDriveBackupToken,
|
name: SettingName.NAMES.OneDriveBackupToken,
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'encrypted-backup-token',
|
value: 'encrypted-backup-token',
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
} as jest.Mocked<Setting>)
|
} as jest.Mocked<Setting>)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: OneDriveBackupFrequency.Disabled,
|
value: 'disabled',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
} as jest.Mocked<Setting>
|
} as jest.Mocked<Setting>
|
||||||
|
|
||||||
await createInterpreter().interpretSettingUpdated(setting, user, OneDriveBackupFrequency.Disabled)
|
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||||
|
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
||||||
@@ -250,7 +267,7 @@ describe('SettingInterpreter', () => {
|
|||||||
it('should not trigger cloud backup if backup frequency setting is updated and a backup token setting is not present', async () => {
|
it('should not trigger cloud backup if backup frequency setting is updated and a backup token setting is not present', async () => {
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
||||||
const setting = {
|
const setting = {
|
||||||
name: SettingName.OneDriveBackupFrequency,
|
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: 'daily',
|
value: 'daily',
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import {
|
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
|
||||||
DropboxBackupFrequency,
|
|
||||||
EmailBackupFrequency,
|
|
||||||
GoogleDriveBackupFrequency,
|
|
||||||
LogSessionUserAgentOption,
|
|
||||||
MuteFailedBackupsEmailsOption,
|
|
||||||
MuteFailedCloudBackupsEmailsOption,
|
|
||||||
OneDriveBackupFrequency,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -22,22 +13,25 @@ import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class SettingInterpreter implements SettingInterpreterInterface {
|
export class SettingInterpreter implements SettingInterpreterInterface {
|
||||||
private readonly cloudBackupTokenSettings = [
|
private readonly cloudBackupTokenSettings = [
|
||||||
SettingName.DropboxBackupToken,
|
SettingName.NAMES.DropboxBackupToken,
|
||||||
SettingName.GoogleDriveBackupToken,
|
SettingName.NAMES.GoogleDriveBackupToken,
|
||||||
SettingName.OneDriveBackupToken,
|
SettingName.NAMES.OneDriveBackupToken,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly cloudBackupFrequencySettings = [
|
private readonly cloudBackupFrequencySettings = [
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly cloudBackupFrequencyDisabledValues = [
|
private readonly cloudBackupFrequencyDisabledValues = ['disabled']
|
||||||
DropboxBackupFrequency.Disabled,
|
|
||||||
GoogleDriveBackupFrequency.Disabled,
|
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
|
||||||
OneDriveBackupFrequency.Disabled,
|
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||||
]
|
[SettingName.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
|
||||||
|
[SettingName.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
|
||||||
|
[SettingName.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
|
||||||
|
])
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||||
@@ -48,6 +42,10 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async interpretSettingUpdated(updatedSetting: Setting, user: User, unencryptedValue: string | null): Promise<void> {
|
async interpretSettingUpdated(updatedSetting: Setting, user: User, unencryptedValue: string | null): Promise<void> {
|
||||||
|
if (this.isChangingMuteEmailsSetting(updatedSetting)) {
|
||||||
|
await this.triggerEmailSubscriptionChange(user, updatedSetting.name as SettingName, unencryptedValue)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isEnablingEmailBackupSetting(updatedSetting)) {
|
if (this.isEnablingEmailBackupSetting(updatedSetting)) {
|
||||||
await this.triggerEmailBackup(user.uuid)
|
await this.triggerEmailBackup(user.uuid)
|
||||||
}
|
}
|
||||||
@@ -65,11 +63,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let userHasEmailsMuted = false
|
let userHasEmailsMuted = false
|
||||||
let muteEmailsSettingUuid = ''
|
let muteEmailsSettingUuid = ''
|
||||||
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
userUuid,
|
userUuid,
|
||||||
)
|
)
|
||||||
if (muteFailedEmailsBackupSetting !== null) {
|
if (muteFailedEmailsBackupSetting !== null) {
|
||||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === MuteFailedBackupsEmailsOption.Muted
|
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === 'muted'
|
||||||
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid
|
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,22 +76,43 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isChangingMuteEmailsSetting(setting: Setting): boolean {
|
||||||
|
return [
|
||||||
|
SettingName.MuteFailedBackupsEmails,
|
||||||
|
SettingName.MuteFailedCloudBackupsEmails,
|
||||||
|
SettingName.MuteMarketingEmails,
|
||||||
|
SettingName.MuteSignInEmails,
|
||||||
|
].includes(setting.name as SettingName)
|
||||||
|
}
|
||||||
|
|
||||||
private isEnablingEmailBackupSetting(setting: Setting): boolean {
|
private isEnablingEmailBackupSetting(setting: Setting): boolean {
|
||||||
return setting.name === SettingName.EmailBackupFrequency && setting.value !== EmailBackupFrequency.Disabled
|
return setting.name === SettingName.NAMES.EmailBackupFrequency && setting.value !== 'disabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
||||||
return (
|
return (
|
||||||
(this.cloudBackupFrequencySettings.includes(setting.name as SettingName) ||
|
(this.cloudBackupFrequencySettings.includes(setting.name) ||
|
||||||
this.cloudBackupTokenSettings.includes(setting.name as SettingName)) &&
|
this.cloudBackupTokenSettings.includes(setting.name)) &&
|
||||||
!this.cloudBackupFrequencyDisabledValues.includes(
|
!this.cloudBackupFrequencyDisabledValues.includes(setting.value as string)
|
||||||
setting.value as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDisablingSessionUserAgentLogging(setting: Setting): boolean {
|
private isDisablingSessionUserAgentLogging(setting: Setting): boolean {
|
||||||
return SettingName.LogSessionUserAgent === setting.name && LogSessionUserAgentOption.Disabled === setting.value
|
return SettingName.NAMES.LogSessionUserAgent === setting.name && 'disabled' === setting.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private async triggerEmailSubscriptionChange(
|
||||||
|
user: User,
|
||||||
|
settingName: SettingName,
|
||||||
|
unencryptedValue: string | null,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.domainEventPublisher.publish(
|
||||||
|
this.domainEventFactory.createMuteEmailsSettingChangedEvent({
|
||||||
|
username: user.email,
|
||||||
|
mute: unencryptedValue === 'muted',
|
||||||
|
emailSubscriptionRejectionLevel: this.emailSettingToSubscriptionRejectionLevelMap.get(settingName) as string,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async triggerSessionUserAgentCleanup(user: User) {
|
private async triggerSessionUserAgentCleanup(user: User) {
|
||||||
@@ -109,29 +128,26 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let cloudProvider
|
let cloudProvider
|
||||||
let tokenSettingName
|
let tokenSettingName
|
||||||
switch (setting.name) {
|
switch (setting.name) {
|
||||||
case SettingName.DropboxBackupToken:
|
case SettingName.NAMES.DropboxBackupToken:
|
||||||
case SettingName.DropboxBackupFrequency:
|
case SettingName.NAMES.DropboxBackupFrequency:
|
||||||
cloudProvider = 'DROPBOX'
|
cloudProvider = 'DROPBOX'
|
||||||
tokenSettingName = SettingName.DropboxBackupToken
|
tokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||||
break
|
break
|
||||||
case SettingName.GoogleDriveBackupToken:
|
case SettingName.NAMES.GoogleDriveBackupToken:
|
||||||
case SettingName.GoogleDriveBackupFrequency:
|
case SettingName.NAMES.GoogleDriveBackupFrequency:
|
||||||
cloudProvider = 'GOOGLE_DRIVE'
|
cloudProvider = 'GOOGLE_DRIVE'
|
||||||
tokenSettingName = SettingName.GoogleDriveBackupToken
|
tokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||||
break
|
break
|
||||||
case SettingName.OneDriveBackupToken:
|
case SettingName.NAMES.OneDriveBackupToken:
|
||||||
case SettingName.OneDriveBackupFrequency:
|
case SettingName.NAMES.OneDriveBackupFrequency:
|
||||||
cloudProvider = 'ONE_DRIVE'
|
cloudProvider = 'ONE_DRIVE'
|
||||||
tokenSettingName = SettingName.OneDriveBackupToken
|
tokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let backupToken = null
|
let backupToken = null
|
||||||
if (this.cloudBackupFrequencySettings.includes(setting.name as SettingName)) {
|
if (this.cloudBackupFrequencySettings.includes(setting.name)) {
|
||||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(
|
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(tokenSettingName as string, userUuid)
|
||||||
tokenSettingName as SettingName,
|
|
||||||
userUuid,
|
|
||||||
)
|
|
||||||
if (tokenSetting !== null) {
|
if (tokenSetting !== null) {
|
||||||
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
||||||
}
|
}
|
||||||
@@ -148,11 +164,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
|||||||
let userHasEmailsMuted = false
|
let userHasEmailsMuted = false
|
||||||
let muteEmailsSettingUuid = ''
|
let muteEmailsSettingUuid = ''
|
||||||
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
userUuid,
|
userUuid,
|
||||||
)
|
)
|
||||||
if (muteFailedCloudBackupSetting !== null) {
|
if (muteFailedCloudBackupSetting !== null) {
|
||||||
userHasEmailsMuted = muteFailedCloudBackupSetting.value === MuteFailedCloudBackupsEmailsOption.Muted
|
userHasEmailsMuted = muteFailedCloudBackupSetting.value === 'muted'
|
||||||
muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid
|
muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { ReadStream } from 'fs'
|
import { ReadStream } from 'fs'
|
||||||
|
|
||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
||||||
import { Setting } from './Setting'
|
import { Setting } from './Setting'
|
||||||
|
|
||||||
export interface SettingRepositoryInterface {
|
export interface SettingRepositoryInterface {
|
||||||
findOneByUuid(uuid: string): Promise<Setting | null>
|
findOneByUuid(uuid: string): Promise<Setting | null>
|
||||||
findOneByUuidAndNames(uuid: string, names: SettingName[]): Promise<Setting | null>
|
findOneByUuidAndNames(uuid: string, names: string[]): Promise<Setting | null>
|
||||||
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||||
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||||
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
||||||
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
|
streamAllByNameAndValue(name: string, value: string): Promise<ReadStream>
|
||||||
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
||||||
save(setting: Setting): Promise<Setting>
|
save(setting: Setting): Promise<Setting>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { LogSessionUserAgentOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
@@ -54,9 +54,9 @@ describe('SettingService', () => {
|
|||||||
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
{
|
{
|
||||||
value: MuteSignInEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
},
|
},
|
||||||
@@ -67,11 +67,11 @@ describe('SettingService', () => {
|
|||||||
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -173,9 +173,7 @@ describe('SettingService', () => {
|
|||||||
|
|
||||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||||
|
|
||||||
expect(
|
expect(await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' })).toEqual({
|
||||||
await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' as SettingName }),
|
|
||||||
).toEqual({
|
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
value: 'decrypted',
|
value: 'decrypted',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { SettingName } from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -74,7 +73,7 @@ export class SettingService implements SettingServiceInterface {
|
|||||||
|
|
||||||
const existing = await this.findSettingWithDecryptedValue({
|
const existing = await this.findSettingWithDecryptedValue({
|
||||||
userUuid: user.uuid,
|
userUuid: user.uuid,
|
||||||
settingName: props.name as SettingName,
|
settingName: props.name,
|
||||||
settingUuid: props.uuid,
|
settingUuid: props.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
|
|
||||||
import { SettingsAssociationService } from './SettingsAssociationService'
|
import { SettingsAssociationService } from './SettingsAssociationService'
|
||||||
@@ -11,52 +11,54 @@ describe('SettingsAssociationService', () => {
|
|||||||
const createService = () => new SettingsAssociationService()
|
const createService = () => new SettingsAssociationService()
|
||||||
|
|
||||||
it('should tell if a setting is mutable by the client', () => {
|
it('should tell if a setting is mutable by the client', () => {
|
||||||
expect(createService().isSettingMutableByClient(SettingName.DropboxBackupFrequency)).toBeTruthy()
|
expect(createService().isSettingMutableByClient(SettingName.NAMES.DropboxBackupFrequency)).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should tell if a setting is immutable by the client', () => {
|
it('should tell if a setting is immutable by the client', () => {
|
||||||
expect(createService().isSettingMutableByClient(SettingName.ListedAuthorSecrets)).toBeFalsy()
|
expect(createService().isSettingMutableByClient(SettingName.NAMES.ListedAuthorSecrets)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return default encryption version for a setting which enecryption version is not strictly defined', () => {
|
it('should return default encryption version for a setting which enecryption version is not strictly defined', () => {
|
||||||
expect(createService().getEncryptionVersionForSetting(SettingName.MfaSecret)).toEqual(EncryptionVersion.Default)
|
expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.MfaSecret)).toEqual(
|
||||||
|
EncryptionVersion.Default,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a defined encryption version for a setting which enecryption version is strictly defined', () => {
|
it('should return a defined encryption version for a setting which enecryption version is strictly defined', () => {
|
||||||
expect(createService().getEncryptionVersionForSetting(SettingName.EmailBackupFrequency)).toEqual(
|
expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||||
EncryptionVersion.Unencrypted,
|
EncryptionVersion.Unencrypted,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return default sensitivity for a setting which sensitivity is not strictly defined', () => {
|
it('should return default sensitivity for a setting which sensitivity is not strictly defined', () => {
|
||||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupToken)).toBeTruthy()
|
expect(createService().getSensitivityForSetting(SettingName.NAMES.DropboxBackupToken)).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a defined sensitivity for a setting which sensitivity is strictly defined', () => {
|
it('should return a defined sensitivity for a setting which sensitivity is strictly defined', () => {
|
||||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupFrequency)).toBeFalsy()
|
expect(createService().getSensitivityForSetting(SettingName.NAMES.DropboxBackupFrequency)).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the default set of settings for a newly registered user', () => {
|
it('should return the default set of settings for a newly registered user', () => {
|
||||||
const settings = createService().getDefaultSettingsAndValuesForNewUser()
|
const settings = createService().getDefaultSettingsAndValuesForNewUser()
|
||||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the default set of settings for a newly registered vault account', () => {
|
it('should return the default set of settings for a newly registered vault account', () => {
|
||||||
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
||||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||||
|
|
||||||
expect(settings.get(SettingName.LogSessionUserAgent)?.value).toEqual('disabled')
|
expect(settings.get(SettingName.NAMES.LogSessionUserAgent)?.value).toEqual('disabled')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a permission name associated to a given setting', () => {
|
it('should return a permission name associated to a given setting', () => {
|
||||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.EmailBackupFrequency)).toEqual(
|
expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||||
PermissionName.DailyEmailBackup,
|
PermissionName.DailyEmailBackup,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not return a permission name if not associated to a given setting', () => {
|
it('should not return a permission name if not associated to a given setting', () => {
|
||||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.ExtensionKey)).toBeUndefined()
|
expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.ExtensionKey)).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import {
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
LogSessionUserAgentOption,
|
|
||||||
MuteMarketingEmailsOption,
|
|
||||||
MuteSignInEmailsOption,
|
|
||||||
SettingName,
|
|
||||||
} from '@standardnotes/settings'
|
|
||||||
import { injectable } from 'inversify'
|
import { injectable } from 'inversify'
|
||||||
|
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
@@ -15,79 +10,79 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
||||||
private readonly UNENCRYPTED_SETTINGS = [
|
private readonly UNENCRYPTED_SETTINGS = [
|
||||||
SettingName.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly UNSENSITIVE_SETTINGS = [
|
private readonly UNSENSITIVE_SETTINGS = [
|
||||||
SettingName.DropboxBackupFrequency,
|
SettingName.NAMES.DropboxBackupFrequency,
|
||||||
SettingName.GoogleDriveBackupFrequency,
|
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||||
SettingName.OneDriveBackupFrequency,
|
SettingName.NAMES.OneDriveBackupFrequency,
|
||||||
SettingName.EmailBackupFrequency,
|
SettingName.NAMES.EmailBackupFrequency,
|
||||||
SettingName.MuteFailedBackupsEmails,
|
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||||
SettingName.MuteFailedCloudBackupsEmails,
|
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
SettingName.ListedAuthorSecrets,
|
SettingName.NAMES.ListedAuthorSecrets,
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.ListedAuthorSecrets]
|
private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.NAMES.ListedAuthorSecrets]
|
||||||
|
|
||||||
private readonly permissionsAssociatedWithSettings = new Map<SettingName, PermissionName>([
|
private readonly permissionsAssociatedWithSettings = new Map<string, PermissionName>([
|
||||||
[SettingName.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
[SettingName.NAMES.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
||||||
])
|
])
|
||||||
|
|
||||||
private readonly defaultSettings = new Map<SettingName, SettingDescription>([
|
private readonly defaultSettings = new Map<string, SettingDescription>([
|
||||||
[
|
[
|
||||||
SettingName.MuteSignInEmails,
|
SettingName.NAMES.MuteSignInEmails,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: MuteSignInEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
SettingName.MuteMarketingEmails,
|
SettingName.NAMES.MuteMarketingEmails,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: MuteMarketingEmailsOption.NotMuted,
|
value: 'not_muted',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Enabled,
|
value: 'enabled',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<SettingName, SettingDescription>([
|
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
||||||
[
|
[
|
||||||
SettingName.LogSessionUserAgent,
|
SettingName.NAMES.LogSessionUserAgent,
|
||||||
{
|
{
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: LogSessionUserAgentOption.Disabled,
|
value: 'disabled',
|
||||||
replaceable: false,
|
replaceable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
isSettingMutableByClient(settingName: SettingName): boolean {
|
isSettingMutableByClient(settingName: string): boolean {
|
||||||
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -95,7 +90,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getSensitivityForSetting(settingName: SettingName): boolean {
|
getSensitivityForSetting(settingName: string): boolean {
|
||||||
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -103,7 +98,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion {
|
getEncryptionVersionForSetting(settingName: string): EncryptionVersion {
|
||||||
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
||||||
return EncryptionVersion.Unencrypted
|
return EncryptionVersion.Unencrypted
|
||||||
}
|
}
|
||||||
@@ -111,7 +106,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return EncryptionVersion.Default
|
return EncryptionVersion.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined {
|
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined {
|
||||||
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -119,11 +114,11 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
|||||||
return this.permissionsAssociatedWithSettings.get(settingName)
|
return this.permissionsAssociatedWithSettings.get(settingName)
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription> {
|
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription> {
|
||||||
return this.defaultSettings
|
return this.defaultSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription> {
|
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
|
||||||
const defaultVaultSettings = new Map(this.defaultSettings)
|
const defaultVaultSettings = new Map(this.defaultSettings)
|
||||||
|
|
||||||
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { SettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
import { SettingDescription } from './SettingDescription'
|
import { SettingDescription } from './SettingDescription'
|
||||||
|
|
||||||
export interface SettingsAssociationServiceInterface {
|
export interface SettingsAssociationServiceInterface {
|
||||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription>
|
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
||||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription>
|
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
|
||||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined
|
||||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
getEncryptionVersionForSetting(settingName: string): EncryptionVersion
|
||||||
getSensitivityForSetting(settingName: SettingName): boolean
|
getSensitivityForSetting(settingName: string): boolean
|
||||||
isSettingMutableByClient(settingName: SettingName | SubscriptionSettingName): boolean
|
isSettingMutableByClient(settingName: string): boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -102,7 +102,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -127,7 +127,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -152,7 +152,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{
|
{
|
||||||
value: '0',
|
value: '0',
|
||||||
sensitive: 0,
|
sensitive: 0,
|
||||||
@@ -266,7 +266,7 @@ describe('SubscriptionSettingService', () => {
|
|||||||
await createService().findSubscriptionSettingWithDecryptedValue({
|
await createService().findSubscriptionSettingWithDecryptedValue({
|
||||||
userSubscriptionUuid: '2-3-4',
|
userSubscriptionUuid: '2-3-4',
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
subscriptionSettingName: 'test' as SubscriptionSettingName,
|
subscriptionSettingName: 'test',
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
serverEncryptionVersion: 1,
|
serverEncryptionVersion: 1,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SubscriptionName, Uuid } from '@standardnotes/common'
|
import { SubscriptionName, Uuid } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
|||||||
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: (await userSubscription.user).uuid,
|
userUuid: (await userSubscription.user).uuid,
|
||||||
userSubscriptionUuid: userSubscription.uuid,
|
userSubscriptionUuid: userSubscription.uuid,
|
||||||
subscriptionSettingName: props.name as SubscriptionSettingName,
|
subscriptionSettingName: props.name,
|
||||||
settingUuid: props.uuid,
|
settingUuid: props.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async findPreviousSubscriptionSetting(
|
private async findPreviousSubscriptionSetting(
|
||||||
settingName: SubscriptionSettingName,
|
settingName: string,
|
||||||
currentUserSubscriptionUuid: Uuid,
|
currentUserSubscriptionUuid: Uuid,
|
||||||
userUuid: Uuid,
|
userUuid: Uuid,
|
||||||
): Promise<SubscriptionSetting | null> {
|
): Promise<SubscriptionSetting | null> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||||
@@ -50,14 +50,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
|||||||
|
|
||||||
const flatSettings = [
|
const flatSettings = [
|
||||||
...(
|
...(
|
||||||
settings as Map<
|
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||||
SubscriptionSettingName,
|
|
||||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
|
||||||
>
|
|
||||||
).keys(),
|
).keys(),
|
||||||
]
|
]
|
||||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: '107374182400',
|
value: '107374182400',
|
||||||
@@ -78,14 +75,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
|||||||
|
|
||||||
const flatSettings = [
|
const flatSettings = [
|
||||||
...(
|
...(
|
||||||
settings as Map<
|
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||||
SubscriptionSettingName,
|
|
||||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
|
||||||
>
|
|
||||||
).keys(),
|
).keys(),
|
||||||
]
|
]
|
||||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: 0,
|
serverEncryptionVersion: 0,
|
||||||
value: '104857600',
|
value: '104857600',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||||
import { PermissionName } from '@standardnotes/features'
|
import { PermissionName } from '@standardnotes/features'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -19,15 +19,12 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private readonly settingsToSubscriptionNameMap = new Map<
|
private readonly settingsToSubscriptionNameMap = new Map<SubscriptionName, Map<string, SettingDescription>>([
|
||||||
SubscriptionName,
|
|
||||||
Map<SubscriptionSettingName, SettingDescription>
|
|
||||||
>([
|
|
||||||
[
|
[
|
||||||
SubscriptionName.PlusPlan,
|
SubscriptionName.PlusPlan,
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -36,7 +33,7 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
SubscriptionName.ProPlan,
|
SubscriptionName.ProPlan,
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
SubscriptionSettingName.FileUploadBytesUsed,
|
SettingName.NAMES.FileUploadBytesUsed,
|
||||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
@@ -45,14 +42,14 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
|||||||
|
|
||||||
async getDefaultSettingsAndValuesForSubscriptionName(
|
async getDefaultSettingsAndValuesForSubscriptionName(
|
||||||
subscriptionName: SubscriptionName,
|
subscriptionName: SubscriptionName,
|
||||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined> {
|
): Promise<Map<string, SettingDescription> | undefined> {
|
||||||
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
||||||
|
|
||||||
if (defaultSettings === undefined) {
|
if (defaultSettings === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultSettings.set(SubscriptionSettingName.FileUploadBytesLimit, {
|
defaultSettings.set(SettingName.NAMES.FileUploadBytesLimit, {
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||||
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
|
||||||
|
|
||||||
import { SettingDescription } from './SettingDescription'
|
import { SettingDescription } from './SettingDescription'
|
||||||
|
|
||||||
export interface SubscriptionSettingsAssociationServiceInterface {
|
export interface SubscriptionSettingsAssociationServiceInterface {
|
||||||
getDefaultSettingsAndValuesForSubscriptionName(
|
getDefaultSettingsAndValuesForSubscriptionName(
|
||||||
subscriptionName: SubscriptionName,
|
subscriptionName: SubscriptionName,
|
||||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined>
|
): Promise<Map<string, SettingDescription> | undefined>
|
||||||
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-11
@@ -5,7 +5,7 @@ import { TimerInterface } from '@standardnotes/time'
|
|||||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||||
|
|
||||||
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
|
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
|
||||||
import { DomainEventPublisherInterface, OfflineSubscriptionTokenCreatedEvent } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
|
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
|
||||||
@@ -47,9 +47,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
domainEventPublisher.publish = jest.fn()
|
domainEventPublisher.publish = jest.fn()
|
||||||
|
|
||||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||||
domainEventFactory.createOfflineSubscriptionTokenCreatedEvent = jest
|
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||||
.fn()
|
|
||||||
.mockReturnValue({} as jest.Mocked<OfflineSubscriptionTokenCreatedEvent>)
|
|
||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||||
@@ -71,10 +69,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
expiresAt: 1,
|
expiresAt: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).toHaveBeenCalledWith(
|
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||||
'random-string',
|
|
||||||
'test@test.com',
|
|
||||||
)
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,7 +86,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -110,7 +105,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +124,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-1
@@ -1,4 +1,5 @@
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
@@ -6,6 +7,7 @@ import { Logger } from 'winston'
|
|||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||||
|
import { getBody, getSubject } from '../../Email/OfflineSubscriptionTokenCreated'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
@@ -62,7 +64,13 @@ export class CreateOfflineSubscriptionToken implements UseCaseInterface {
|
|||||||
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
|
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createOfflineSubscriptionTokenCreatedEvent(token, dto.userEmail),
|
this.domainEventFactory.createEmailRequestedEvent({
|
||||||
|
body: getBody(dto.userEmail, `https://standardnotes.com/dashboard/offline?subscription_token=${token}`),
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
subject: getSubject(),
|
||||||
|
messageIdentifier: 'OFFLINE_SUBSCRIPTION_ACCESS',
|
||||||
|
userEmail: dto.userEmail,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SubscriptionName } from '@standardnotes/common'
|
|||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||||
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
||||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
import { SettingName } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
@@ -56,7 +56,7 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: regularSubscriptionUserUuid,
|
userUuid: regularSubscriptionUserUuid,
|
||||||
userSubscriptionUuid: regularSubscription.uuid,
|
userSubscriptionUuid: regularSubscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||||
})
|
})
|
||||||
if (uploadBytesUsedSetting !== null) {
|
if (uploadBytesUsedSetting !== null) {
|
||||||
uploadBytesUsed = +(uploadBytesUsedSetting.value as string)
|
uploadBytesUsed = +(uploadBytesUsedSetting.value as string)
|
||||||
@@ -70,7 +70,7 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||||
userUuid: regularSubscriptionUserUuid,
|
userUuid: regularSubscriptionUserUuid,
|
||||||
userSubscriptionUuid: regularSubscription.uuid,
|
userSubscriptionUuid: regularSubscription.uuid,
|
||||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||||
})
|
})
|
||||||
if (overwriteWithUserUploadBytesLimitSetting !== null) {
|
if (overwriteWithUserUploadBytesLimitSetting !== null) {
|
||||||
uploadBytesLimit = +(overwriteWithUserUploadBytesLimitSetting.value as string)
|
uploadBytesLimit = +(overwriteWithUserUploadBytesLimitSetting.value as string)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user